# Events

Implement callbacks to customise your Drop-in payment flow.

## Overview

Drop-in provides a unified event system that works consistently across all payment methods. You can use these callbacks to inject your own business logic and user experience customisations into the payment flow at critical moments. They ensure that while the SDK handles the complex technical aspects of payment processing, you retain full control over the customer experience and can seamlessly integrate payments into your broader business workflows and systems.

Callbacks enable you to:

* Validate business rules before payments proceed.
* Display custom loading states and progress indicators.
* Show user-friendly error messages.
* Track analytics and monitor payment performance.
* Integrate with your own systems for fraud detection or customer management.
* Implement card-on-file functionality for returning customers.


Your callbacks receive normalised data regardless of whether the customer paid with cards, PayPal, or Google Pay.

All events are optional except `onSuccess` and `onError`, which are required for proper payment handling.

## Supported events

### onGetShopper

This callback provides shopper information for card-on-file functionality, allowing returning customers to use saved payment methods.

You can use it to:

* Provide shopper ID for logged-in users.
* Generate anonymous shopper ID for guest checkout.
* Enable card-on-file for returning customers.
* Associate payment methods with customer accounts.


#### Event data

This callback receives no parameters and must return a `Shopper` object.

#### Return value

| Property | Description |
|  --- | --- |
| `id`String | A unique shopper identifier. Use customer ID for logged-in users or generate a unique ID for guests. |


#### Example implementation

```kotlin
onGetShopper = {
    // For logged-in users, return their customer ID
    val customerId = getCurrentCustomerId()
    if (customerId != null) {
        return@onGetShopper Shopper(id = customerId)
    }
    
    // For guest users, generate or retrieve a session-based ID
    val guestId = getOrCreateGuestId()
    Shopper(id = guestId)
}
```

The shopper ID is used to store and retrieve saved payment methods. For guests, generate a unique ID (e.g., UUID) that persists across the session. For registered users, use their customer/account ID from your system.

### onBeforeSubmit

This callback is triggered when a payment method is selected and the user is about to submit payment. This is your last chance to validate data or cancel the payment before it's processed.

You can use it to:

* Perform final validation before payment submission.
* Check inventory availability.
* Verify customer account status.
* Display confirmation dialogs.
* Track analytics events.
* Perform fraud checks.


#### Event data

| Parameter | Description |
|  --- | --- |
| `paymentMethod`PaymentMethod | The selected payment method (Card, Paypal, or GooglePay). |


#### Return value

* `true` or no return: Continue with payment submission.
* `false`: Cancel payment submission.
* Suspend function: Async validation (resolved value determines whether to proceed).


#### Example implementation

```kotlin
onBeforeSubmit = { paymentMethod ->
    Log.d("CheckoutDropIn", "Payment method selected: $paymentMethod")
    
    // Track analytics
    analytics.track("payment_initiated", mapOf(
        "payment_method" to paymentMethod.toString(),
        "amount" to 25.00
    ))
    
    // Show confirmation for large amounts
    if (amount > 100) {
        val confirmed = showConfirmationDialog(
            "Confirm payment of $amount USD?"
        )
        if (!confirmed) return@onBeforeSubmit false
    }
    
    // Check inventory availability
    val hasStock = checkInventory()
    if (!hasStock) {
        showToast("Some items are no longer available")
        return@onBeforeSubmit false
    }
    
    true // Proceed with payment
}
```

Simple validation example:

```kotlin
onBeforeSubmit = { paymentMethod ->
    // Simple synchronous validation
    if (!isValidOrder()) {
        showToast("Please complete all required fields")
        return@onBeforeSubmit false // Payment won't proceed
    }
    
    true // Payment will proceed
}
```

### onSubmit

This callback is triggered when payment processing begins, after `onBeforeSubmit` validation passes. Use this to show loading indicators and disable UI elements.

You can use it to:

* Show loading spinners or progress indicators.
* Disable submit buttons to prevent double-submission.
* Display "Processing payment..." messages.
* Update UI to show payment is in progress.
* Track analytics for payment submission.


#### Event data

| Parameter | Description |
|  --- | --- |
| `paymentMethod`PaymentMethod | The payment method being processed (Card, Paypal, or GooglePay). |


#### Example implementation

```kotlin
onSubmit = { paymentMethod ->
    Log.d("CheckoutDropIn", "Processing payment with: $paymentMethod")
    
    // Show loading overlay
    showLoadingOverlay(true)
    
    // Disable submit button to prevent double-submission
    disableSubmitButton()
    
    // Track analytics
    analytics.track("payment_processing", mapOf(
        "payment_method" to paymentMethod.toString()
    ))
}
```

Complete loading state management:

```kotlin
var loadingTimeout: Job? = null

onSubmit = { paymentMethod ->
    // Show loading state
    setLoadingState(true)
    
    // Set timeout in case payment takes too long
    loadingTimeout = lifecycleScope.launch {
        delay(10000) // 10 seconds
        showWarning("Payment is taking longer than expected. Please wait...")
    }
    
    // Track start time for performance monitoring
    paymentStartTime = System.currentTimeMillis()
}
```

### onSuccess

This callback is triggered after payment succeeds. It receives the final transaction result from the payment processing system.

You can use it to:

* Verify payment on your backend (REQUIRED).
* Redirect to success page after verification.
* Display success messages.
* Track successful payment analytics.
* Update stock levels for purchased items.
* Send order confirmation emails to customers.
* Clear shopping cart.


#### Event data

| Event data | Description |
|  --- | --- |
| `result`DropInSubmitResult | The payment processing result from PXP's backend. |
| `result.systemTransactionId`String | Unity's system transaction identifier. Use for backend verification. |
| `result.merchantTransactionId`String? | Your unique transaction identifier (optional). |
| `result.authenticationId`String? | 3DS authentication identifier when authentication was performed. |
| `result.paymentMethod`PaymentMethod | The payment method used.Possible values:`PaymentMethod.CARD``PaymentMethod.PAYPAL``PaymentMethod.GOOGLE_PAY` |
| `result.paymentData`AuthorisationPaymentData? | Google Pay only: Contains additional payment data including email, shipping address, and shipping option. This is `null` for card, PayPal, and other non-Google Pay methods. |


`DropInSubmitResult` type:

```kotlin
data class DropInSubmitResult(
    val systemTransactionId: String,
    val merchantTransactionId: String? = null,
    val authenticationId: String? = null,
    val paymentMethod: PaymentMethod,
    val paymentData: AuthorisationPaymentData? = null
)
```

#### Google Pay payment data

When a payment is completed using Google Pay, the `result.paymentData` field contains additional information collected during the Google Pay flow:

```kotlin
data class AuthorisationPaymentData(
    val email: String? = null,
    val shippingAddress: Map<String, String?>? = null,
    val shippingOption: Map<String, String?>? = null
)
```

These fields are populated based on your Google Pay configuration:

| Field | Populated when |
|  --- | --- |
| `email` | `emailRequired` is `true` in your Google Pay configuration. |
| `shippingAddress` | `shippingAddressRequired` is `true` in your Google Pay configuration. |
| `shippingOption` | Google Pay shipping options are configured and the user selects one. |


Example handling Google Pay payment data:

```kotlin
onSuccess = { result ->
    Log.d("Checkout", "Payment successful: ${result.systemTransactionId}")
    Log.d("Checkout", "Payment method: ${result.paymentMethod}")
    
    // Check for Google Pay-specific data
    if (result.paymentMethod == PaymentMethod.GOOGLE_PAY && result.paymentData != null) {
        val paymentData = result.paymentData
        
        // Email address if requested
        paymentData.email?.let { email ->
            Log.d("Checkout", "Customer email: $email")
            // Use email for order confirmation
        }
        
        // Shipping address if requested
        paymentData.shippingAddress?.let { address ->
            Log.d("Checkout", "Shipping address: $address")
            // Use address for order fulfillment
        }
        
        // Shipping option if configured
        paymentData.shippingOption?.let { option ->
            Log.d("Checkout", "Shipping option: $option")
            // Use selected shipping method
        }
    }
    
    // CRITICAL: Verify on backend before fulfilling
    verifyPaymentOnBackend(result)
}
```

The `onSuccess` callback is a frontend event and can be manipulated by malicious users. Never fulfil orders based solely on this callback. Always verify payments on your backend using Unity webhooks or the [Get transaction details API](/apis/transaction/other/get-transaction-details) before fulfilling orders.

#### Example implementation

```kotlin
onSuccess = { result ->
    Log.d("CheckoutDropIn", "Payment successful (frontend notification only)")
    Log.d("CheckoutDropIn", "System transaction ID: ${result.systemTransactionId}")
    Log.d("CheckoutDropIn", "Merchant transaction ID: ${result.merchantTransactionId}")
    Log.d("CheckoutDropIn", "Payment method: ${result.paymentMethod}")
    
    // Clear loading timeout if set
    loadingTimeout?.cancel()
    
    // Hide loading overlay
    setLoadingState(false)
    
    // Track analytics
    val processingTime = System.currentTimeMillis() - paymentStartTime
    analytics.track("payment_success_frontend", mapOf(
        "system_transaction_id" to result.systemTransactionId,
        "merchant_transaction_id" to result.merchantTransactionId,
        "payment_method" to result.paymentMethod.toString(),
        "processing_time" to processingTime
    ))
    
    // Show temporary success message
    showMessage("Payment received! Verifying...", MessageType.SUCCESS)
    
    // CRITICAL: Verify payment on backend before fulfilling order
    lifecycleScope.launch {
        try {
            val verification = verifyPaymentOnBackend(
                systemTransactionId = result.systemTransactionId,
                merchantTransactionId = result.merchantTransactionId
            )
            
            if (verification.success) {
                // Payment verified on backend - safe to proceed
                Log.d("CheckoutDropIn", "Payment verified on backend: ${verification.orderId}")
                
                // Track backend verification success
                analytics.track("payment_verified", mapOf(
                    "order_id" to verification.orderId,
                    "system_transaction_id" to result.systemTransactionId
                ))
                
                // Clear cart
                clearShoppingCart()
                
                // Navigate to success page
                navigateToOrderConfirmation(verification.orderId)
            } else {
                // Verification failed
                Log.e("CheckoutDropIn", "Backend verification failed: ${verification.error}")
                
                analytics.track("payment_verification_failed", mapOf(
                    "system_transaction_id" to result.systemTransactionId,
                    "error" to verification.error
                ))
                
                showError(
                    "Payment verification failed. Please contact support with " +
                    "transaction ID: ${result.merchantTransactionId}"
                )
            }
        } catch (e: Exception) {
            // Network or server error during verification
            Log.e("CheckoutDropIn", "Failed to verify payment", e)
            
            analytics.track("payment_verification_error", mapOf(
                "system_transaction_id" to result.systemTransactionId,
                "error" to e.message
            ))
            
            // Show error but don't clear cart (webhook may still process it)
            showError(
                "Unable to verify payment. Your payment may still be processing. " +
                "Please check your email for confirmation or contact support with " +
                "transaction ID: ${result.merchantTransactionId}"
            )
        }
    }
}
```

### onError

This callback is triggered when an error occurs during the payment process.

You can use it to:

* Display user-friendly error messages.
* Log errors for debugging and monitoring.
* Track failed payment analytics.
* Offer alternative payment methods.
* Provide retry options.
* Hide loading indicators.
* Re-enable form controls.


#### Event data

| Parameter | Description |
|  --- | --- |
| `error`BaseSdkException | The error object containing details about what went wrong. |
| `error.message`String | A human-readable error message. |
| `error.code`String | SDK error code in format `SDK####` (e.g., `'SDK1113'` for authentication failed, `'SDK0500'` for network error). Use this for programmatic error handling. |


`BaseSdkException` type:

```kotlin
interface BaseSdkException {
    val message: String
    val code: String
}
```

#### Common error scenarios

The following table shows common error scenarios and how to detect and respond to them:

| Scenario | Detection approach | User action |
|  --- | --- | --- |
| Card declined | `error.message` contains "declined" or specific provider messages | Try a different card. |
| Insufficient funds | `error.message` contains "insufficient funds" | Use a different payment method. |
| Expired card | `error.message` contains "expired" | Use a different card. |
| Invalid CVV | `error.message` contains "CVV" or "security code" | Check security code. |
| 3DS authentication failed | `error.code == "SDK1113"` or `error.message` contains "Authentication failed" | Try again or use different card. |
| 3DS timeout | `error.message` contains "timeout" | Check connection and retry. |
| User cancelled 3DS | `error.message` contains "cancel" | Retry payment. |
| PayPal error | `error.code == "SDK1116"` or payment method is PayPal | Try again or use different method. |
| Session expired | `error.message` contains "session" or "expired" | Refresh page and retry. |
| Network error | `error.code == "SDK0500"` | Check connection and retry. |
| Configuration error | `error.code` starts with `"SDK01"` or `"SDK02"` | Contact support. |


#### Example implementation

```kotlin
onError = { error ->
    Log.e("CheckoutDropIn", "Payment failed")
    Log.e("CheckoutDropIn", "Error code: ${error.code}")
    Log.e("CheckoutDropIn", "Error message: ${error.message}")
    
    // Clear loading timeout if set
    loadingTimeout?.cancel()
    
    // Hide loading overlay
    setLoadingState(false)
    
    // Re-enable submit button
    enableSubmitButton()
    
    // Track error analytics
    analytics.track("payment_failed", mapOf(
        "error_code" to error.code,
        "error_message" to error.message
    ))
    
    // Log error for monitoring
    logErrorToMonitoring(
        category = "payment_error",
        code = error.code,
        message = error.message,
        userAgent = Build.MODEL
    )
    
    // Show user-friendly error message based on error code and message
    val userMessage = when {
        // Check by SDK error code
        error.code == "SDK0500" -> 
            "Network connection issue. Please check your internet connection and try again."
        error.code == "SDK1113" -> 
            "3D Secure authentication failed. Please try again or use a different card."
        error.code == "SDK1115" -> {
            // Card payment failed - check message for specifics
            when {
                error.message.contains("declined", ignoreCase = true) ->
                    "Your card was declined. Please try a different card or contact your bank for more information."
                error.message.contains("insufficient funds", ignoreCase = true) ->
                    "Insufficient funds. Please use a different payment method."
                error.message.contains("expired", ignoreCase = true) ->
                    "This card has expired. Please use a different card."
                error.message.contains("cvv", ignoreCase = true) || 
                error.message.contains("security code", ignoreCase = true) ->
                    "Invalid security code. Please check the CVV on the back of your card and try again."
                else ->
                    "Card payment failed. Please check your details and try again."
            }
        }
        error.code == "SDK1116" ->
            "PayPal payment failed. Please try again or use a different payment method."
        error.code == "SDK1117" ->
            "Google Pay payment failed. Please try again or use a different payment method."
        error.message.contains("timeout", ignoreCase = true) ->
            "Request timed out. Please check your internet connection and try again."
        error.message.contains("session", ignoreCase = true) || 
        error.message.contains("expired", ignoreCase = true) ->
            "Your payment session has expired. Please refresh the page and try again."
        else -> error.message
    }
    
    showErrorMessage(userMessage)
}
```

Error handling with retry logic:

```kotlin
var retryCount = 0
val MAX_RETRIES = 3

onError = { error ->
    // Check if error is retryable (network issues, timeouts)
    val isNetworkError = error.code == "SDK0500"
    val isTimeout = error.message.contains("timeout", ignoreCase = true)
    val isRetryable = isNetworkError || isTimeout
    
    if (isRetryable && retryCount < MAX_RETRIES) {
        retryCount++
        
        Log.d("CheckoutDropIn", "Retryable error, attempt $retryCount/$MAX_RETRIES")
        
        showMessage(
            "Connection issue. Attempt $retryCount/$MAX_RETRIES. Please try your payment again.",
            MessageType.WARNING
        )
    } else {
        retryCount = 0 // Reset retry count
        
        if (isRetryable) {
            showMessage(
                "Unable to process payment after multiple attempts. " +
                "Please check your connection and try again later.",
                MessageType.ERROR
            )
        } else {
            showMessage(
                "Payment failed: ${error.message}",
                MessageType.ERROR
            )
        }
        
        // Show alternative options for persistent failures
        showAlternativePaymentMethods()
    }
}
```

### onGetConsent

This callback controls whether to show a consent checkbox for a specific payment method. This is typically used to get user permission to save payment information for future use (card-on-file).

You can use it to:

* Show consent checkbox only for specific payment methods.
* Comply with regulations requiring explicit consent for storing payment data.
* Allow users to opt-in to card-on-file functionality.
* Control consent display based on user type (guest vs registered).


#### Event data

| Parameter | Description |
|  --- | --- |
| `paymentMethod`PaymentMethod | The payment method being evaluated.Possible values:`PaymentMethod.CARD``PaymentMethod.PAYPAL``PaymentMethod.GOOGLE_PAY` |


#### Return value

* `true`: Show consent checkbox for this payment method.
* `false`: Hide consent checkbox for this payment method.


#### Example implementation

```kotlin
methodConfig = DropInMethodConfig(
    global = DropInGlobalConfig(
        // Control consent checkbox for card-on-file
        onGetConsent = { paymentMethod ->
            // Show consent for cards and PayPal, but not for wallet payments
            paymentMethod == PaymentMethod.CARD || paymentMethod == PaymentMethod.PAYPAL
        }
    )
)
```

Dynamic consent based on user type:

```kotlin
onGetConsent = { paymentMethod ->
    // Only show consent for logged-in users
    val isLoggedIn = checkUserLoginStatus()
    
    if (!isLoggedIn) {
        return@onGetConsent false // Guest users can't save payment methods
    }
    
    // Show consent for cards only
    paymentMethod == PaymentMethod.CARD
}
```

### onCancel

This callback is triggered when a payment is cancelled by the user. This allows you to track cancellations, update UI, or perform cleanup operations.

You can use it to:

* Track payment abandonment analytics.
* Show user-friendly cancellation message.
* Re-enable form fields or buttons.
* Clear loading indicators.
* Offer alternative payment methods.
* Send abandonment emails for cart recovery.


#### Event data

| Parameter | Description |
|  --- | --- |
| `paymentMethod`PaymentMethod | The payment method that was cancelled. |
| `data`Any? | Additional cancellation data (varies by payment method). |


This callback is triggered when:

* User closes PayPal popup without completing payment.
* User cancels Google Pay payment sheet.
* User closes 3D Secure authentication window.
* User explicitly cancels the payment flow.


#### Example implementation

```kotlin
methodConfig = DropInMethodConfig(
    global = DropInGlobalConfig(
        onCancel = { paymentMethod, data ->
            Log.d("CheckoutDropIn", "Payment cancelled: $paymentMethod")
            
            // Track analytics
            analytics.track("payment_cancelled", mapOf(
                "method" to paymentMethod.toString(),
                "timestamp" to System.currentTimeMillis()
            ))
            
            // Hide loading spinner
            setIsProcessing(false)
            
            // Re-enable checkout button
            setCheckoutButtonDisabled(false)
            
            // Show cancellation message
            showNotification(
                type = NotificationType.INFO,
                message = "$paymentMethod payment was cancelled. " +
                         "Please try again or choose a different payment method."
            )
            
            // Reset payment form
            resetPaymentForm()
        }
    )
)
```

## Callback execution order

Understanding the callback flow helps you implement proper payment handling:

1. `onGetShopper`: initial setup
  * Called during Drop-in initialisation.
  * Must return shopper ID for card-on-file functionality.
2. `onBeforeSubmit`: validation phase
  * User selects payment method and clicks submit.
  * Return `true` to proceed, `false` to cancel.
  * Perform business validation and fraud checks.
3. `onSubmit`: processing starts (if `onBeforeSubmit` returns `true`)
  * Show loading indicators.
  * Disable form fields.
  * Track analytics.
4. `onSuccess` or `onError`: payment completes
  * `onSuccess`: payment succeeded (verify on backend before fulfilling).
  * `onError`: payment failed (show error message and offer alternatives).
5. `onCancel`: user cancels (optional)
  * Triggered if user closes payment window/sheet.
  * Update UI and track analytics.


## Complete integration example

Here's a complete example showing all callbacks working together:

```kotlin
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.ui.Modifier
import com.pxp.checkout.checkoutdropin.CheckoutDropIn
import com.pxp.checkout.checkoutdropin.types.*
import com.pxp.checkout.models.*
import java.time.Instant
import java.util.UUID
import kotlinx.coroutines.*

var loadingTimeout: Job? = null
var retryCount = 0
const val MAX_RETRIES = 3

suspend fun initializeCheckout(context: Context) {
    // Get session from backend
    val sessionData = fetchSessionFromBackend()
    
    // Initialise Drop-in with all callbacks
    val checkoutDropIn = CheckoutDropIn.initialize(
        context = context,
        config = CheckoutDropInConfig(
            environment = Environment.LIVE,
            session = sessionData,
            ownerType = "MerchantGroup",
            ownerId = "MERCHANT-1",
            transactionData = DropInTransactionData(
                amount = 25.0,
                currency = "USD",
                entryType = EntryType.Ecom,
                intent = DropInTransactionIntentData(
                    card = IntentType.Authorisation,
                    paypalDropInIntent = DropInPayPalIntentType.Authorisation
                ),
                merchant = "MERCHANT-1",
                merchantTransactionId = UUID.randomUUID().toString(),
                merchantTransactionDate = { Instant.now().toString() }
            ),
            kountDisabled = false, // OPTIONAL: Set to true to disable Kount fraud detection
            
            // Required: Get shopper information
            onGetShopper = {
                val customerId = getCurrentCustomerId()
                Shopper(
                    id = customerId ?: "guest-${UUID.randomUUID()}"
                )
            },
            
            // Before payment submission
            onBeforeSubmit = { paymentMethod ->
                Log.d("CheckoutDropIn", "Payment method selected: $paymentMethod")
                
                // Track analytics
                analytics.track("payment_initiated", mapOf(
                    "payment_method" to paymentMethod.toString(),
                    "amount" to 25.00
                ))
                
                // Show confirmation for large amounts
                if (25.00 > 100) {
                    val confirmed = showConfirmation(
                        "Confirm payment of 25.00 USD?"
                    )
                    if (!confirmed) return@CheckoutDropInConfig false
                }
                
                // Validate inventory
                val hasStock = checkInventory()
                if (!hasStock) {
                    showToast("Some items are no longer available")
                    return@CheckoutDropInConfig false
                }
                
                true // Proceed with payment
            },
            
            // Payment processing started
            onSubmit = { paymentMethod ->
                Log.d("CheckoutDropIn", "Processing payment...")
                
                // Show loading state
                showLoadingOverlay(true)
                disableSubmitButton()
                
                // Set timeout warning
                loadingTimeout = lifecycleScope.launch {
                    delay(10000)
                    showMessage("Payment is taking longer than expected...", MessageType.WARNING)
                }
                
                // Track processing start
                paymentStartTime = System.currentTimeMillis()
                
                analytics.track("payment_processing", mapOf(
                    "payment_method" to paymentMethod.toString()
                ))
            },
            
            // Payment succeeded (frontend notification)
            onSuccess = { result ->
                Log.d("CheckoutDropIn", "Payment successful (verifying on backend...)")
                
                // Clear timeout
                loadingTimeout?.cancel()
                
                // Calculate processing time
                val processingTime = System.currentTimeMillis() - paymentStartTime
                Log.d("CheckoutDropIn", "Payment processed in ${processingTime}ms")
                
                // Track frontend success
                analytics.track("payment_success_frontend", mapOf(
                    "system_transaction_id" to result.systemTransactionId,
                    "payment_method" to result.paymentMethod.toString(),
                    "processing_time" to processingTime
                ))
                
                // Show verifying message
                showMessage("Payment received! Verifying...", MessageType.SUCCESS)
                
                // CRITICAL: Verify on backend
                lifecycleScope.launch {
                    try {
                        val verified = verifyPaymentOnBackend(
                            systemTransactionId = result.systemTransactionId,
                            merchantTransactionId = result.merchantTransactionId
                        )
                        
                        if (verified.success) {
                            // Backend verification passed
                            analytics.track("payment_verified", mapOf(
                                "order_id" to verified.orderId
                            ))
                            
                            // Clear cart and redirect
                            clearCart()
                            navigateToSuccess(verified.orderId)
                        } else {
                            throw Exception(verified.error ?: "Verification failed")
                        }
                    } catch (e: Exception) {
                        Log.e("CheckoutDropIn", "Verification error", e)
                        showLoadingOverlay(false)
                        showError(
                            "Payment verification failed. Please contact support with " +
                            "transaction ID: ${result.merchantTransactionId}"
                        )
                    }
                }
            },
            
            // Payment failed
            onError = { error ->
                Log.e("CheckoutDropIn", "Payment failed: ${error.code}")
                Log.e("CheckoutDropIn", "Error message: ${error.message}")
                
                // Clear timeout
                loadingTimeout?.cancel()
                
                // Hide loading state
                showLoadingOverlay(false)
                enableSubmitButton()
                
                // Track error
                analytics.track("payment_failed", mapOf(
                    "error_code" to error.code,
                    "error_message" to error.message
                ))
                
                // Log for monitoring
                logError(error)
                
                // Handle retryable errors
                val isNetworkError = error.code == "SDK0500"
                val isTimeout = error.message.contains("timeout", ignoreCase = true)
                if ((isNetworkError || isTimeout) && retryCount < MAX_RETRIES) {
                    retryCount++
                    showMessage(
                        "Connection issue (attempt $retryCount/$MAX_RETRIES). Please try again.",
                        MessageType.WARNING
                    )
                    return@CheckoutDropInConfig
                }
                
                retryCount = 0 // Reset
                
                // Show user-friendly error
                val userMessage = when {
                    error.code == "SDK1113" ->
                        "3D Secure authentication failed."
                    error.code == "SDK1115" ->
                        "Card payment failed."
                    error.code == "SDK1116" ->
                        "PayPal payment failed."
                    error.message.contains("declined", ignoreCase = true) ->
                        "Card declined. Please try a different card."
                    error.message.contains("insufficient funds", ignoreCase = true) ->
                        "Insufficient funds. Please use a different payment method."
                    error.message.contains("expired", ignoreCase = true) ->
                        "This card has expired."
                    error.message.contains("cvv", ignoreCase = true) ->
                        "Invalid security code."
                    else -> "Payment failed. Please try again."
                }
                
                showErrorMessage(userMessage)
                
                // Offer alternatives for card issues
                if (error.code == "SDK1115" || 
                    error.message.contains("card", ignoreCase = true)) {
                    showAlternativePaymentMethods()
                }
            },
            
            // Method-specific configuration
            methodConfig = DropInMethodConfig(
                global = DropInGlobalConfig(
                    // Control consent checkbox
                    onGetConsent = { paymentMethod ->
                        // Show consent for cards and PayPal
                        paymentMethod == PaymentMethod.CARD || 
                        paymentMethod == PaymentMethod.PAYPAL
                    },
                    
                    // Handle cancellation
                    onCancel = { paymentMethod, data ->
                        Log.d("CheckoutDropIn", "Payment cancelled: $paymentMethod")
                        
                        // Clear timeout
                        loadingTimeout?.cancel()
                        
                        // Hide loading
                        showLoadingOverlay(false)
                        enableSubmitButton()
                        
                        // Track cancellation
                        analytics.track("payment_cancelled", mapOf(
                            "payment_method" to paymentMethod.toString()
                        ))
                        
                        showMessage("Payment was cancelled. Please try again.", MessageType.INFO)
                    }
                )
            )
        )
    )
    
    // Create component
    val component = checkoutDropIn.create()
    
    // Render in Compose
    component.Content(modifier = Modifier.fillMaxWidth())
}
```