Skip to content

Non-3DS transactions

Integrate a frictionless checkout experience.

Overview

By implementing non-3DS transactions into your payment flow, you benefit from:

  • Streamlined checkout: No additional authentication required, providing faster payment completion
  • Lower transaction friction: Reduced cart abandonment with seamless payment flow
  • Faster processing: Immediate payment completion without authentication delays
  • Better mobile experience: Optimised for mobile checkouts where 3DS challenges can be problematic

Non-3DS transactions are ideal for low-risk scenarios, trusted customers, or when implementing other fraud prevention measures. However, they may have different liability arrangements and potentially higher processing fees.

Payment flow

The non-3DS flow is made up of six key steps.

Step 1: Submission

The customer taps the submit button or the payment is triggered programmatically. The submit() method in CardSubmitComponent is invoked and validation occurs inside it. If validation passes, the method continues with the payment processing. If it fails, the method exits early and doesn't proceed with the transaction.

Step 2: Card tokenisation

If it's a new card, the SDK sends the card details to the tokenisation service. If it's a saved card, the SDK retrieves the existing token.

Step 3: Evaluation

The SDK evaluates whether 3DS authentication is required. In this flow, no 3DS authentication is performed - the payment proceeds directly to authorisation.

Step 4: Authorisation

The SDK sends the transaction to the payment processor for authorisation without requiring additional customer authentication.

Step 5: Capture

Depending on your configuration, the transaction may be automatically captured or require manual capture later.

Step 6: Completion

The payment is completed and your application receives the final result through the onPostAuthorisation callback.

Non-3DS callback details

Non-3DS transactions use simplified callbacks without authentication complexity:

Core callbacks

  • onPostAuthorisation: (SubmitResult) -> Unit

    • Purpose: Receives the final transaction result from the payment gateway
    • Parameter: SubmitResult (one of AuthorisedSubmitResult, CapturedSubmitResult, RefusedSubmitResult, FailedSubmitResult)
    • Contains: Transaction status, provider response, transaction reference, processing details
    • Usage: Handle payment success/failure, navigate to appropriate screens, update order status
  • onPreAuthorisation: (PreAuthorisationData) -> TransactionInitiationData? (optional)

    • Purpose: Last chance to modify transaction data before authorization
    • Parameter: PreAuthorisationData containing transaction details (no 3DS data)
    • Return: TransactionInitiationData with additional metadata, exemption data, or custom parameters
    • Return null: To proceed with default authorization settings

Additional callbacks

  • onPreTokenisation: () -> Boolean (optional)

    • Purpose: Called before card tokenization begins
    • Return: true to proceed with tokenization, false to abort
    • Usage: Perform final validation, show loading states
  • onPostTokenisation: (CardTokenisationResult) -> Unit (optional)

    • Purpose: Receives tokenization results
    • Parameter: CardTokenisationResult (either success with token ID or failure with error)
    • Usage: Handle tokenization success/failure, store token references
  • onSubmitError: (BaseSdkException) -> Unit (optional)

    • Purpose: Handles submission errors
    • Parameter: BaseSdkException containing error details and codes
    • Usage: Display error messages, implement retry logic, track failures

SDK configuration

To process non-3DS transactions, configure your SDK without 3DS callbacks:

val sdkConfig = PxpSdkConfig(
    environment = Environment.TEST,
    session = SessionConfig(
        sessionId = "your_session_id",
        sessionData = "your_session_data"
    ),
    transactionData = TransactionData(
        amount = 99.99,
        currency = CurrencyType.USD,
        entryType = EntryType.Ecom,
        intent = IntentType.Authorisation,
        merchantTransactionId = "order-123",
        merchantTransactionDate = { Instant.now().toString() },
        shopper = Shopper(
            email = "customer@example.com",
            firstName = "John",
            lastName = "Doe"
        )
    ),
    clientId = "your_client_id",
    ownerId = "Unity",
    ownerType = "MerchantGroup",
    merchantShopperId = "shopper-123"
)

val pxpCheckout = PxpCheckout.builder()
    .withConfig(sdkConfig)
    .withContext(this)
    .withDebugMode(true)
    .build()

Step 1: Create components

val cardNumberConfig = CardNumberComponentConfig(
    label = "Card number",
    isRequired = true,
    validateOnChange = true
)

val cardExpiryConfig = CardExpiryDateComponentConfig(
    label = "Expiry date",
    isRequired = true,
    validateOnChange = true
)

val cardCvcConfig = CardCvcComponentConfig(
    label = "Security code",
    isRequired = true,
    validateOnChange = true
)

val cardSubmitConfig = CardSubmitComponentConfig(
    buttonText = "Pay £99.99",
    // Note: No 3DS callbacks = non-3DS transaction
    onPostAuthorisation = { result ->
        when (result) {
            is AuthorizedSubmitResult -> {
                Log.d("NonThreeDS", "Payment successful: ${result.providerResponse.message}")
                navigateToSuccessScreen()
            }
            is CapturedSubmitResult -> {
                Log.d("NonThreeDS", "Payment captured: ${result.providerResponse.message}")
                navigateToSuccessScreen()
            }
            is RefusedSubmitResult -> {
                Log.e("NonThreeDS", "Payment declined: ${result.stateData.message}")
                showError("Payment was declined by your bank")
            }
            is FailedSubmitResult -> {
                Log.e("NonThreeDS", "Payment failed: ${result.errorReason}")
                showError("Payment failed. Please try again.")
            }
            else -> {
                Log.e("NonThreeDS", "Unknown result: ${result::class.simpleName}")
                showError("Payment completed with unknown status.")
            }
        }
    },
    onSubmitError = { error ->
        Log.e("NonThreeDS", "Payment error: ${error.message}")
        showError("Payment failed: ${error.message}")
    }
)

val cardNumberComponent = pxpCheckout.createComponent(ComponentType.CARD_NUMBER, cardNumberConfig)
val cardExpiryComponent = pxpCheckout.createComponent(ComponentType.CARD_EXPIRY_DATE, cardExpiryConfig)
val cardCvcComponent = pxpCheckout.createComponent(ComponentType.CARD_CVC, cardCvcConfig)
val cardSubmitComponent = pxpCheckout.createComponent(ComponentType.CARD_SUBMIT, cardSubmitConfig)

Step 2: Handle common scenarios

Low-value transactions

For low-value transactions where 3DS exemptions apply:

class LowValuePaymentManager {
    
    fun createNon3DSConfig(amount: Double): CardSubmitComponentConfig {
        return CardSubmitComponentConfig(
            buttonText = "Pay ${formatCurrency(amount)}",
            onPostAuthorisation = { result ->
                when (result) {
                    is AuthorizedSubmitResult -> {
                        Log.d("LowValue", "Low-value payment successful: ${result.providerResponse.message}")
                        // Track successful low-value payment
                        trackPaymentSuccess(amount, "low_value_non_3ds")
                        navigateToSuccessScreen()
                    }
                    is RefusedSubmitResult -> {
                        Log.e("LowValue", "Low-value payment declined: ${result.stateData.message}")
                        showError("Payment declined. Please try a different card.")
                    }
                    else -> {
                        Log.e("LowValue", "Low-value payment failed")
                        showError("Payment failed. Please try again.")
                    }
                }
            }
        )
    }
    
    private fun formatCurrency(amount: Double): String {
        return "£%.2f".format(amount)
    }
    
    private fun trackPaymentSuccess(amount: Double, type: String) {
        // Analytics tracking for successful payments
        Log.d("Analytics", "Payment successful: amount=$amount, type=$type")
    }
}

Trusted customer transactions

For returning customers with established trust:

class TrustedCustomerPaymentManager(private val customerId: String) {
    
    fun createTrustedCustomerConfig(): CardSubmitComponentConfig {
        return CardSubmitComponentConfig(
            buttonText = "Pay securely",
            onPreAuthorisation = { preAuthData ->
                // Add trusted customer metadata
                TransactionInitiationData(
                    psd2Data = preAuthData.psd2Data,
                    metadata = mapOf(
                        "customer_id" to customerId,
                        "trusted_customer" to "true",
                        "risk_level" to "low",
                        "transaction_type" to "non_3ds_trusted"
                    )
                )
            },
    onPostAuthorisation = { result ->
                when (result) {
                    is AuthorizedSubmitResult -> {
                        Log.d("Trusted", "Trusted customer payment successful")
                        updateCustomerTrustScore(customerId, "successful_payment")
                        navigateToSuccessScreen()
                    }
                    is RefusedSubmitResult -> {
                        Log.e("Trusted", "Trusted customer payment declined")
                        updateCustomerTrustScore(customerId, "declined_payment")
                        showError("Payment declined. Please verify your card details.")
                    }
                    else -> {
                        Log.e("Trusted", "Unknown payment result")
                        showError("Payment completed with unknown status.")
                    }
                }
            }
        )
    }
    
    private fun updateCustomerTrustScore(customerId: String, event: String) {
        Log.d("TrustScore", "Updating trust score for $customerId: $event")
    }
}

Step 3: Handle errors

Implement comprehensive error handling for non-3DS transactions:

class Non3DSErrorHandler {
    
    fun handlePaymentError(error: PaymentError) {
        when (error.code) {
            "CARD_DECLINED" -> {
                showError("Your card was declined. Please try a different payment method.")
            }
            "INSUFFICIENT_FUNDS" -> {
                showError("Insufficient funds. Please try a different card.")
            }
            "INVALID_CARD" -> {
                showError("Invalid card details. Please check and try again.")
            }
            "EXPIRED_CARD" -> {
                showError("Your card has expired. Please use a different card.")
            }
            "BLOCKED_CARD" -> {
                showError("Your card is blocked. Please contact your bank.")
            }
            "NETWORK_ERROR" -> {
                showError("Network error. Please check your connection and try again.")
            }
            "VALIDATION_FAILED" -> {
                showError("Please check all required fields are completed correctly.")
            }
            else -> {
                showError("Payment failed. Please try again or use a different payment method.")
            }
        }
    }
    
    private fun showError(message: String) {
        // Implementation depends on your UI framework
        Log.e("Non3DS", message)
    }
}

Complete example

The following example shows a complete non-3DS implementation:

class Non3DSPaymentActivity : ComponentActivity() {
    private lateinit var pxpCheckout: PxpCheckout
    private lateinit var newCardComponent: NewCardComponent
    private val errorHandler = Non3DSErrorHandler()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setupPxpCheckout()
        setupComponents()
        
        setContent {
            Non3DSPaymentScreen()
        }
    }
    
    private fun setupPxpCheckout() {
        val sdkConfig = PxpSdkConfig(
            environment = Environment.TEST,
            session = SessionConfig(
                sessionId = "session-${System.currentTimeMillis()}",
                sessionData = "your_session_data"
            ),
            transactionData = TransactionData(
                amount = 29.99,
                currency = CurrencyType.GBP,
                entryType = EntryType.ECOM,
                intent = IntentType.AUTHORISATION,
                merchantTransactionId = "order-${System.currentTimeMillis()}",
                merchantTransactionDate = { Instant.now().toString() },
                shopper = Shopper(
                    email = "customer@example.com",
                    firstName = "Jane",
                    lastName = "Smith"
                )
            ),
            clientId = "your_client_id",
            ownerId = "Unity",
            ownerType = "MerchantGroup",
            merchantShopperId = "shopper-456"
        )
        
        pxpCheckout = PxpCheckout.builder()
            .withConfig(sdkConfig)
            .withContext(this)
            .withDebugMode(true)
            .build()
    }
    
    private fun setupComponents() {
        val newCardConfig = NewCardComponentConfig(
            submit = CardSubmitComponentConfig(
                // No 3DS callbacks - transaction will be non-3DS
                onPostAuthorisation = { result ->
                    when (result) {
                        is AuthorizedSubmitResult -> {
                            Log.d("Non3DS", "Payment successful: ${result.providerResponse.message}")
                            navigateToSuccessScreen()
                        }
                        is CapturedSubmitResult -> {
                            Log.d("Non3DS", "Payment captured: ${result.providerResponse.message}")
                            navigateToSuccessScreen()
                        }
                        is RefusedSubmitResult -> {
                            Log.e("Non3DS", "Payment declined: ${result.stateData.message}")
                            showDeclinedMessage(result.stateData.code)
                        }
                        is FailedSubmitResult -> {
                            Log.e("Non3DS", "Payment failed: ${result.errorReason}")
                            showError("Payment failed. Please try again.")
                        }
                        else -> {
                            Log.e("Non3DS", "Unknown result: ${result::class.simpleName}")
                            showError("Payment completed with unknown status.")
                        }
                    }
                },
                onSubmitError = { error ->
                    Log.e("Non3DS", "Submit error: ${error.message}")
                    errorHandler.handlePaymentError(error)
                }
            )
        )
        
        newCardComponent = pxpCheckout.createComponent(ComponentType.NEW_CARD, newCardConfig)
    }
    
    @Composable
    private fun Non3DSPaymentScreen() {
        var isLoading by remember { mutableStateOf(false) }
        var errorMessage by remember { mutableStateOf<String?>(null) }
        
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            Text(
                text = "Express checkout",
                style = MaterialTheme.typography.headlineMedium,
                modifier = Modifier.padding(bottom = 8.dp)
            )
            
            Text(
                text = "Fast and secure payment without additional verification",
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                modifier = Modifier.padding(bottom = 16.dp)
            )
            
            // Show error message if any
            errorMessage?.let { message ->
                Card(
                    colors = CardDefaults.cardColors(containerColor = Color.Red.copy(alpha = 0.1f)),
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(bottom = 16.dp)
                ) {
                    Text(
                        text = message,
                        color = Color.Red,
                        modifier = Modifier.padding(16.dp)
                    )
                }
            }
            
            // Render payment components
            pxpCheckout.buildComponentView(
                component = newCardComponent,
                modifier = Modifier.fillMaxWidth()
            )
            
            // Loading indicator
            if (isLoading) {
                Box(
                    modifier = Modifier.fillMaxWidth(),
                    contentAlignment = Alignment.Center
                ) {
                    CircularProgressIndicator()
            Text(
                        text = "Processing payment...",
                        modifier = Modifier.padding(top = 60.dp)
                    )
                }
            }
        }
    }
    
    private fun navigateToSuccessScreen(transactionId: String?) {
        val intent = Intent(this, PaymentSuccessActivity::class.java).apply {
            putExtra("transaction_id", transactionId)
            putExtra("payment_type", "non_3ds")
        }
        startActivity(intent)
        finish()
    }
    
    private fun showDeclinedMessage(reasonCode: String?) {
        val message = when (reasonCode) {
            "insufficient_funds" -> "Insufficient funds. Please try a different card."
            "expired_card" -> "Your card has expired. Please use a different card."
            "invalid_card" -> "Invalid card details. Please check and try again."
            else -> "Payment was declined. Please try a different card."
        }
        showError(message)
    }
    
    private fun showError(message: String) {
        AlertDialog.Builder(this)
            .setTitle("Payment Error")
            .setMessage(message)
            .setPositiveButton("OK") { dialog, _ ->
                dialog.dismiss()
            }
            .show()
    }
}

Callback data

This section describes the data received by the different callbacks as part of the non-3DS flow.

onPreAuthorisation

The onPreAuthorisation callback receives:

  • Pre-authorisation data (preAuthData): Transaction data ready for authorisation.
  • Error data (error): Any error that occurred during pre-authorisation.

For non-3DS transactions, the 3DS-related data will be null.

Pre-authorisation data

The pre-authorisation data includes transaction initiation data (for new cards) or card token data (for saved cards).

With transaction initiation data:

PreAuthorisationData(
    transactionInitiationData = TransactionInitiationData(
        threeDSecureData = null, // Always null for non-3DS
        psd2Data = PSD2Data(
            scaExemption = "LowValue"
        ),
        identityVerification = IdentityVerification(
            nameVerification = true
        ),
        addressVerification = AddressVerification(
            countryCode = "US",
            houseNumberOrName = "123",
            postalCode = "10001"
        )
    )
)

With card token data:

PreAuthorisationData(
    transactionInitiationData = null,
    cardTokenData = CardTokenData(
        gatewayTokenId = "gw_token_abc123def456789",
        schemeTokenId = null,
        maskedPrimaryAccountNumber = "****-****-****-4242",
        cardExpiryMonth = "12",
        cardExpiryYear = "2025",
        scheme = "VISA",
        fundingSource = "CREDIT",
        ownerType = "PERSONAL",
        issuerName = "Chase Bank",
        issuerCountryCode = "US",
        lastSuccessfulPurchaseDate = "2024-01-15T10:30:00Z",
        lastSuccessfulPayoutDate = null
    )
)
ParameterDescription
transactionInitiationDataDetails about the transaction, if associated with a new card, null otherwise.
transactionInitiationData.threeDSecureDataThis is always null for non-3DS transactions.
transactionInitiationData.psd2DataDetails about PSD2. This is required for non-3DS transactions.
transactionInitiationData.psd2Data.scaExemptionThe type of SCA exemption that applies to this transaction.

Possible values:
  • AnonymousCard
  • LowValue
  • SecureCorporate
  • TransactionRiskAnalysis
  • TrustedBeneficiary
transactionInitiationData.identityVerificationDetails about the identity verification.
transactionInitiationData.identityVerification.nameVerificationWhether the cardholder's name matches the name associated with the registered address on file.
transactionInitiationData.addressVerificationDetails about the address verification.
transactionInitiationData.addressVerification.countryCodeThe country code associated with the cardholder's address, in ISO-3166-1 alpha-2 format.
transactionInitiationData.addressVerification.houseNumberOrNameThe cardholder's street address.
transactionInitiationData.addressVerification.postalCodeThe postal or ZIP code associated with the cardholder's address.
cardTokenDataDetails about the card token if associated with a saved card, null otherwise.

Here's an example of what to do with this data:

onPreAuthorisation = { preAuthData, error ->
    Log.d("Payment", "Card transaction data: $preAuthData")
    
    if (error != null) {
        Log.e("Payment", "Pre-authorisation error: ${error.message}")
        showError("Payment processing error: ${error.message}")
        return@onPreAuthorisation null // Cancel the transaction
    }
    
    val transactionData = preAuthData.transactionInitiationData
    
    // Add fraud prevention data
    val enhancedData = transactionData?.copy(
        // Add device fingerprinting
        deviceData = DeviceData(
            userAgent = getUserAgent(),
            language = Locale.getDefault().language,
            screenResolution = "${getScreenWidth()}x${getScreenHeight()}",
            timezone = TimeZone.getDefault().id
        ),
        
        // Add session information
        sessionData = SessionData(
            sessionId = generateSessionId(),
            timestamp = Instant.now().toString(),
            ipAddress = getClientIP() // You'd implement this
        ),
        
        // Add custom risk indicators
        riskIndicators = RiskIndicators(
            customerType = "returning",
            paymentHistory = "good",
            velocityCheck = "passed"
        )
    )
    
    Log.d("Payment", "Sending enhanced card transaction")
    enhancedData
}

// Helper data classes
data class DeviceData(
    val userAgent: String,
    val language: String,
    val screenResolution: String,
    val timezone: String
)

data class SessionData(
    val sessionId: String,
    val timestamp: String,
    val ipAddress: String?
)

data class RiskIndicators(
    val customerType: String,
    val paymentHistory: String,
    val velocityCheck: String
)

onPostAuthorisation

The onPostAuthorisation callback receives the final transaction result (SubmitResult).

Success

If the transaction was successful, you'll receive either an AuthorizedSubmitResult or a CapturedSubmitResult.

AuthorizedSubmitResult(
    state = "Authorised",
    providerResponse = ProviderResponse(
        code = "00",
        message = "Approved",
        cardVerificationCodeResult = "M",
        addressVerificationServiceResult = "Y"
    ),
    fundingData = FundingData(
        cardVerificationCodeResult = "Matched",
        addressVerificationServiceResult = "Y"
    )
)
ParameterDescription
stateThe final state of the transaction.

Possible values:
  • Authorised
  • Captured
  • Refused
providerResponseDetails about the provider's response.
providerResponse.codeThe raw result code returned by the provider that processed the transaction.
providerResponse.messageThe raw message associated with the result code from the provider that processed the transaction.
providerResponse.cardVerificationCodeResultThe Card Verification Code (CVC) result returned by the provider.
providerResponse.addressVerificationServiceResultThe Address Verification Service (AVS) result returned by the provider.
fundingDataDetails about the payment method.
fundingData.cardVerificationCodeResultThe Card Verification Code (CVC) result in human-readable format.
fundingData.addressVerificationServiceResultThe Address Verification Service (AVS) result in human-readable format.

Here's an example of what to do with this data:

onPostAuthorisation = { result ->
    Log.d("Payment", "Non-3DS payment result: $result")
    
    when (result) {
        is AuthorizedSubmitResult -> {
            Log.d("Payment", "Payment successful!")
            Log.d("Payment", "Provider response: ${result.providerResponse.message}")
            
            // Check verification results
            val fundingData = result.fundingData
            if (fundingData.cardVerificationCodeResult == "Matched") {
                Log.d("Payment", "CVC verification passed")
            }
            
            if (fundingData.addressVerificationServiceResult == "Y") {
                Log.d("Payment", "Address verification passed")
            }
            
            // Store transaction details
            storeTransactionRecord(TransactionRecord(
                amount = 99.99,
                currency = "USD",
                cardType = "VISA",
                processingType = "non-3ds",
                timestamp = Instant.now().toString()
            ))
            
            // Navigate to success screen
            navigateToSuccessScreen()
        }
        is CapturedSubmitResult -> {
            Log.d("Payment", "Payment captured successfully!")
            Log.d("Payment", "Provider response: ${result.providerResponse.message}")
            navigateToSuccessScreen()
        }
        is RefusedSubmitResult -> {
            Log.e("Payment", "Payment declined: ${result.stateData.message}")
            handlePaymentFailure(result)
        }
        is FailedSubmitResult -> {
            Log.e("Payment", "Payment failed: ${result.errorReason}")
            showError("Payment failed: ${result.errorReason}")
        }
        else -> {
            Log.e("Payment", "Unknown result type: ${result::class.simpleName}")
            showError("Payment completed with unknown status")
        }
    }
}

data class TransactionRecord(
    val amount: Double,
    val currency: String,
    val cardType: String,
    val processingType: String,
    val timestamp: String
)

Failure (Declined)

If the bank or issuer declines the transaction, you'll receive a RefusedSubmitResult.

RefusedSubmitResult(
    state = "Refused",
    stateData = StateData(
        code = "05",
        message = "Do not honour"
    ),
    providerResponse = ProviderResponse(
        code = "05",
        message = "Do not honour",
        merchantAdvice = MerchantAdvice(
            code = "01",
            message = "Try another payment method"
        ),
        cardVerificationCodeResult = "M",
        addressVerificationServiceResult = "Y"
    ),
    fundingData = FundingData(
        cardVerificationCodeResult = "Matched",
        addressVerificationServiceResult = "Y"
    )
)

Here's an example of how to handle failures:

private fun handlePaymentFailure(result: RefusedSubmitResult) {
    Log.e("Payment", "Payment declined: ${result.stateData.message}")
    
    // Check merchant advice for next steps
    result.providerResponse?.merchantAdvice?.let { advice ->
        when (advice.code) {
            "01" -> {
                // Try another payment method
                showError("Payment declined. Please try a different card.")
                enableAlternativePaymentMethods()
            }
            "02" -> {
                // Retry with different amount
                showError("Transaction amount issue. Please contact support.")
            }
            "03" -> {
                // Contact issuer
                showError("Please contact your bank to authorise this payment.")
            }
            else -> {
                showError("Payment declined: ${advice.message}")
            }
        }
    } ?: run {
        // Generic decline message
        val declineReason = when (result.stateData.code) {
            "05" -> "Payment declined by your bank"
            "14" -> "Invalid card number"
            "54" -> "Card has expired"
            "61" -> "Amount limit exceeded"
            else -> "Payment was declined"
        }
        showError(declineReason)
    }
    
    // Track decline for analytics
    trackDeclineEvent(DeclineEvent(
        declineCode = result.stateData.code,
        declineReason = result.stateData.message,
        merchantAdvice = result.providerResponse?.merchantAdvice?.code,
        timestamp = Instant.now().toString()
    ))
}

data class DeclineEvent(
    val declineCode: String,
    val declineReason: String,
    val merchantAdvice: String?,
    val timestamp: String
)

private fun enableAlternativePaymentMethods() {
    // Show alternative payment options
    Log.d("Payment", "Enabling alternative payment methods")
}

private fun trackDeclineEvent(event: DeclineEvent) {
    // Track decline for fraud prevention and analytics
    Log.d("Analytics", "Decline event: $event")
}

Error handling

For both callbacks, implement comprehensive error handling to ensure a smooth user experience:

onSubmitError = { error ->
    Log.e("Payment", "Submit error: ${error.message}")
    
    when (error.errorCode) {
        "NETWORK_ERROR" -> {
            showError("Network connection issue. Please check your internet and try again.")
        }
        "VALIDATION_FAILED" -> {
            showError("Please check all required fields are completed correctly.")
        }
        "CARD_EXPIRED" -> {
            showError("Your card has expired. Please use a different card.")
        }
        "INSUFFICIENT_FUNDS" -> {
            showError("Insufficient funds. Please try a different card.")
        }
        else -> {
            showError("Payment failed. Please try again or use a different payment method.")
        }
    }
}