# Events

## Overview

The Google Pay button component emits events based on user interaction and payment processing stages. You can implement callback functions to inject your own business logic and customise the user experience at critical moments throughout the payment flow. 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.

Callbacks enable you to:

* Validate business rules before payments proceed.
* Display custom error, failure, or success messages.
* Integrate with fraud detection systems.
* Control the payment flow based on transaction data.
* Handle 3D Secure authentication.
* Collect and validate additional security data (CVC).
* Manage consent for payment tokens and recurring payments.
* Track analytics and monitor payment performance.
* Implement retry logic for soft declines.


## Payment flow events

### onPreAuthorisation

This callback is triggered before payment authorisation, allowing you to provide transaction initialisation data or control whether to proceed.

You can use it to:

* Integrate with Kount or other fraud detection services.
* Apply business rules based on transaction amount or customer history.
* Check product availability before processing payment.
* Provide risk screening data for fraud prevention.
* Configure PSD2 SCA exemptions for the transaction.
* Cancel the payment if business rules aren't met.


#### Callback signature

```kotlin
onPreAuthorisation: (suspend (PreAuthorisationData?) -> GooglePayTransactionInitData?)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`PreAuthorisationData? | Pre-authorisation data containing token identifiers. |
| `data.gatewayTokenId`String? | The gateway token identifier for the payment. Use this ID to retrieve full token details from the Unity backend. |
| `data.schemeTokenId`String? | The scheme token identifier for the payment (e.g., Visa Token Service, Mastercard Digital Enablement Service). |


#### Return value

| Return type | Description |
|  --- | --- |
| `GooglePayTransactionInitData` | Transaction initialisation data to proceed with payment. |
| `GooglePayTransactionInitData.psd2Data` | PSD2 data including SCA exemption configuration. |
| `GooglePayTransactionInitData.psd2Data.scaExemption` | SCA exemption type: `ANONYMOUS_CARD`, `LOW_VALUE`, `SECURE_CORPORATE`, `TRANSACTION_RISK_ANALYSIS`, `TRUSTED_BENEFICIARY`. |
| `GooglePayTransactionInitData.riskScreeningData` | Risk screening data for fraud detection (Kount). |
| `GooglePayTransactionInitData.riskScreeningData.performRiskScreening` | Whether to perform risk screening (`Boolean`). |
| `GooglePayTransactionInitData.riskScreeningData.excludeDeviceData` | Whether to exclude device data from risk screening (`Boolean`). |
| `null` | Return null to cancel the authorisation. |


#### Example implementation

```kotlin
import com.pxp.checkout.components.googlepay.types.GooglePayTransactionInitData
import com.pxp.checkout.services.kount.*
import com.pxp.checkout.services.models.transaction.ScaExemptionType

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPreAuthorisation = suspend { data ->
        Log.d("GooglePay", "Pre-authorisation started")
        Log.d("GooglePay", "Gateway Token ID: ${data?.gatewayTokenId}")
        Log.d("GooglePay", "Scheme Token ID: ${data?.schemeTokenId}")
        
        // Perform pre-payment validation
        val isHighRisk = checkCustomerRiskProfile()
        val customerTier = getCustomerTier()
        
        // Check inventory before proceeding
        val inventoryAvailable = checkInventoryAvailability()
        if (!inventoryAvailable) {
            showError("Some items are no longer available")
            return@suspend null // Cancel payment
        }
        
        // Check business rules
        val cartTotal = getCurrentCartTotal()
        if (cartTotal > 10000.0 && isHighRisk) {
            showError("Transaction requires additional verification")
            return@suspend null // Cancel payment for manual review
        }
        
        GooglePayTransactionInitData(
            riskScreeningData = RiskScreeningData(
                performRiskScreening = true,
                excludeDeviceData = false,
                userIp = "192.168.1.100",
                account = RiskScreeningAccount(
                    id = "user_12345678",
                    creationDateTime = "2024-01-15T10:30:00.000Z"
                ),
                transaction = RiskScreeningTransaction(
                    subtotal = cartTotal
                ),
                items = cartItems.map { item ->
                    RiskScreeningItem(
                        price = item.price,
                        quantity = item.quantity,
                        category = item.category,
                        sku = item.sku
                    )
                },
                fulfillments = listOf(
                    RiskScreeningFulfillment(
                        type = FulfillmentType.SHIPPED,
                        shipping = RiskScreeningShipping(
                            shippingMethod = ShippingMethod.STANDARD
                        ),
                        recipientPerson = RiskScreeningRecipientPerson(
                            phoneNumber = "+1234567890"
                        )
                    )
                )
            ),
            psd2Data = GooglePayTransactionInitData.Psd2Data(
                scaExemption = if (cartTotal < 30.0) {
                    ScaExemptionType.LOW_VALUE
                } else {
                    ScaExemptionType.TRANSACTION_RISK_ANALYSIS
                }
            )
        )
    }
}
```

### onPostAuthorisation

This callback is triggered after the payment is authorised, providing the transaction result and payment data.

You can use it to:

* Handle successful or declined payments.
* Navigate to success or failure screens.
* Update inventory and order status.
* Send order confirmation emails.
* Record transactions for business intelligence.
* Display custom success or error messages.
* Process shipping information collected from Google Pay.
* Save transaction details for customer account history.
* Update UI based on payment outcome.


#### Callback signature

```kotlin
onPostAuthorisation: ((SubmitResult?, AuthorisationPaymentData) -> Unit)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `result`SubmitResult? | The transaction result: either `MerchantSubmitResult` (success) or `FailedSubmitResult` (failure). |
| `result.merchantTransactionId`String | Your unique transaction identifier (present in both success and failure). |
| `result.systemTransactionId`String | PXP system transaction identifier (only in `MerchantSubmitResult`). |
| `result.errorCode`String | Error code (only in `FailedSubmitResult`). |
| `result.errorReason`String | Error description (only in `FailedSubmitResult`). |
| `result.correlationId`String | Correlation ID for tracking (only in `FailedSubmitResult`). |
| `result.httpStatusCode`Int | HTTP status code (only in `FailedSubmitResult`). |
| `paymentData`AuthorisationPaymentData | Google Pay payment data collected during checkout. |
| `paymentData.email`String? | Customer email address (if email collection was enabled). |
| `paymentData.shippingAddress`Map<String, String?>? | Shipping address (if shipping address collection was enabled). |
| `paymentData.shippingOption`Map<String, String?>? | Selected shipping option (if shipping options were provided). |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPostAuthorisation = { result, paymentData ->
        Log.d("GooglePay", "Payment completed: $result")
        
        when (result) {
            is MerchantSubmitResult -> {
                // Payment successful
                Log.d("GooglePay", "Payment authorised successfully")
                Log.d("GooglePay", "Merchant Transaction ID: ${result.merchantTransactionId}")
                Log.d("GooglePay", "System Transaction ID: ${result.systemTransactionId}")
                
                // Extract payment data
                val email = paymentData.email
                val shippingAddress = paymentData.shippingAddress
                val shippingOption = paymentData.shippingOption
                
                // Use your Activity's lifecycleScope or CoroutineScope
                lifecycleScope.launch {
                    try {
                        // Update inventory
                        updateInventory(result.merchantTransactionId)
                        
                        // Create order record
                        createOrder(
                            transactionId = result.merchantTransactionId,
                            systemTransactionId = result.systemTransactionId,
                            email = email,
                            shippingAddress = shippingAddress,
                            shippingMethod = shippingOption?.get("id"),
                            status = "confirmed"
                        )
                        
                        // Send confirmation email
                        sendConfirmationEmail(
                            transactionId = result.merchantTransactionId,
                            email = email,
                            shippingAddress = shippingAddress
                        )
                        
                        // Track successful payment
                        analyticsTracker.trackEvent("google_pay_success") {
                            put("transaction_id", result.merchantTransactionId)
                            put("cart_value", getCurrentCartValue())
                            put("shipping_method", shippingOption?.get("id"))
                        }
                        
                        // Update customer account
                        updateCustomerPurchaseHistory(result.merchantTransactionId)
                        
                        // Navigate to success screen
                        withContext(Dispatchers.Main) {
                            navController.navigate(
                                "payment_success/${result.merchantTransactionId}"
                            )
                        }
                    } catch (e: Exception) {
                        Log.e("GooglePay", "Error in post-authorisation", e)
                        showError("Payment succeeded but order processing failed")
                    }
                }
            }
            
            is FailedSubmitResult -> {
                // Payment declined
                Log.e("GooglePay", "Payment declined: ${result.errorReason}")
                Log.e("GooglePay", "Error code: ${result.errorCode}")
                Log.e("GooglePay", "Correlation ID: ${result.correlationId}")
                
                // Log failure for monitoring
                logPaymentFailure(
                    errorCode = result.errorCode,
                    errorReason = result.errorReason,
                    correlationId = result.correlationId,
                    merchantTransactionId = result.merchantTransactionId
                )
                
                // Show user-friendly error message
                val errorMessage = getErrorMessage(result.errorCode)
                showError("Payment declined: $errorMessage")
                
                // Track failed payment
                analyticsTracker.trackEvent("google_pay_declined") {
                    put("error_code", result.errorCode)
                    put("correlation_id", result.correlationId)
                }
                
                // Offer alternatives
                showAlternativePaymentMethods()
            }
            
            else -> {
                // Unexpected result
                Log.e("GooglePay", "Unexpected result: $result")
                showError("Payment processing error. Please contact support.")
                analyticsTracker.trackEvent("google_pay_exception") {
                    put("result", result.toString())
                }
            }
        }
    }
}
```

## Google Pay sheet interaction events

### onPaymentDataChanged

This callback is triggered when payment data changes in the Google Pay sheet, such as shipping address or shipping option selection.

You can use it to:

* Calculate shipping costs based on selected address.
* Update transaction amounts dynamically.
* Validate addresses and show errors for unserviceable locations.
* Add or remove line items based on selections.
* Calculate taxes based on shipping destination.
* Apply discounts based on shipping method selection.
* Check delivery availability for specific locations.


When updating transaction amounts, use the SDK's `updateAmount()` method to synchronise the amount with the backend session.

#### Callback signature

```kotlin
onPaymentDataChanged: ((Any) -> Any?)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `intermediatePaymentData`Any | Google Pay intermediate payment data containing shipping address and option selections. This is a dynamic object from the Google Pay API that you'll need to parse based on Google Pay's structure. |


The `intermediatePaymentData` object contains fields like `callbackTrigger`, `shippingAddress`, and `shippingOptionData` that follow Google Pay's API structure. You'll need to cast and parse this `Any` object to access these properties.

#### Return value

Return a Map structure representing updated payment data to display in the Google Pay sheet. The structure should follow Google Pay's `PaymentDataRequestUpdate` format with the following fields:

| Return field | Description |
|  --- | --- |
| `newTransactionInfo` | Map containing updated transaction information. |
| `newTransactionInfo.totalPrice` | Updated total price as a string. |
| `newTransactionInfo.totalPriceStatus` | Price status: `"FINAL"` or `"ESTIMATED"`. |
| `newTransactionInfo.displayItems` | List of Maps representing line items with `label`, `type`, and `price` fields. |
| `newShippingOptionParameters` | Map containing updated shipping options (optional). |
| `error` | Map containing error information for invalid addresses or options (optional). |
| `error.reason` | Error reason: `"SHIPPING_ADDRESS_INVALID"`, `"SHIPPING_ADDRESS_UNSERVICEABLE"`, `"SHIPPING_OPTION_INVALID"`. |
| `error.message` | User-friendly error message. |
| `error.intent` | The callback intent that triggered the error: `"SHIPPING_ADDRESS"`, `"SHIPPING_OPTION"`. |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        // ... other configuration
        shippingAddressRequired = true,
        shippingOptionRequired = true
    )
    
    onPaymentDataChanged = { intermediatePaymentData ->
        Log.d("GooglePay", "Payment data changed")
        
        // Parse the intermediate payment data
        // Note: You'll need to cast/parse based on Google Pay's structure
        
        val baseAmount = 20.00
        var tax = 5.00
        var shippingCost = 0.0
        var errorMap: Map<String, String>? = null
        
        // Example: Handle shipping address or option changes
        // The structure depends on Google Pay's IntermediatePaymentData format
        
        // Calculate new total
        val newTotal = baseAmount + tax + shippingCost
        
        // Update SDK amount
        pxpCheckout.updateAmount(newTotal)
        
        // Return updated payment data as Map
        mapOf(
            "newTransactionInfo" to mapOf(
                "totalPrice" to newTotal.toString(),
                "totalPriceStatus" to "FINAL",
                "displayItems" to listOf(
                    mapOf(
                        "label" to "Subtotal",
                        "type" to "SUBTOTAL",
                        "price" to baseAmount.toString()
                    ),
                    mapOf(
                        "label" to "Shipping",
                        "type" to "LINE_ITEM",
                        "price" to shippingCost.toString()
                    ),
                    mapOf(
                        "label" to "Tax",
                        "type" to "TAX",
                        "price" to tax.toString()
                    )
                )
            ),
            "error" to errorMap // Include error if validation failed
        ).filterValues { it != null } // Remove null values
    }
}
```

### onGooglePaymentButtonClicked

This callback is triggered when the Google Pay button is tapped, before the payment sheet opens.

You can use it to:

* Track button click events for analytics.
* Show loading indicators or progress dialogs.
* Perform final validation before opening payment sheet.
* Update cart totals or recalculate amounts.
* Check inventory availability one last time.
* Prepare data for the payment flow.


#### Callback signature

```kotlin
onGooglePaymentButtonClicked: (() -> kotlinx.coroutines.Deferred<Unit>?)?
```

#### Event data

This callback receives no parameters.

#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onGooglePaymentButtonClicked = {
        Log.d("GooglePay", "Google Pay button clicked")
        
        // Track button click
        analyticsTracker.trackEvent("google_pay_button_clicked") {
            put("timestamp", System.currentTimeMillis())
            put("cart_value", getCurrentCartValue())
            put("item_count", getCartItemCount())
        }
        
        // Show loading indicator
        withContext(Dispatchers.Main) {
            showLoadingDialog()
        }
        
        // Final inventory check
        val inventoryOk = checkInventoryAvailability()
        if (!inventoryOk) {
            withContext(Dispatchers.Main) {
                hideLoadingDialog()
                showError("Some items are no longer available")
            }
            throw IllegalStateException("Inventory unavailable")
        }
        
        // Prepare checkout data
        prepareCheckoutData()
        
        // Update cart totals
        recalculateCartTotals()
        
        withContext(Dispatchers.Main) {
            hideLoadingDialog()
        }
        
        CompletableDeferred(Unit)
    }
}
```

## Validation and error events

### onCustomValidation

This callback is executed before opening the Google Pay payment sheet, allowing custom validation of other form fields.

You can use it to:

* Validate required form fields before payment.
* Check terms and conditions acceptance.
* Verify customer information completeness.
* Prevent payment sheet from opening if validation fails.
* Validate minimum order amounts.
* Check age verification requirements.


#### Callback signature

```kotlin
onCustomValidation: (() -> suspend Boolean)?
```

#### Event data

This callback receives no parameters.

#### Return value

| Return value | Description |
|  --- | --- |
| `Boolean` | Return `true` to proceed with opening payment sheet, `false` to prevent opening. |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onCustomValidation = suspend {
        var isValid = true
        
        // Validate email
        val email = emailField.text.toString()
        if (email.isEmpty() || !email.contains('@')) {
            showError("Please enter a valid email address")
            highlightField(emailField)
            isValid = false
        }
        
        // Validate phone number (if required)
        val phone = phoneField.text.toString()
        if (phone.isEmpty() || phone.length < 10) {
            showError("Please enter a valid phone number")
            highlightField(phoneField)
            isValid = false
        }
        
        // Validate terms acceptance
        val termsAccepted = termsCheckbox.isChecked
        if (!termsAccepted) {
            showError("Please accept the terms and conditions")
            highlightField(termsCheckbox)
            isValid = false
        }
        
        // Validate age verification (if required)
        val ageVerified = ageVerifyCheckbox.isChecked
        if (!ageVerified) {
            showError("You must be 18 or older to complete this purchase")
            isValid = false
        }
        
        // Validate minimum order amount
        val cartTotal = getCurrentCartTotal()
        if (cartTotal < 10.00) {
            showError("Minimum order amount is £10.00")
            isValid = false
        }
        
        // Final inventory check
        if (isValid) {
            val inventoryAvailable = checkInventory()
            if (!inventoryAvailable) {
                showError("Some items are no longer available. Please review your cart.")
                isValid = false
            }
        }
        
        isValid // Return validation result
    }
}
```

### onError

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

You can use it to:

* Log errors for debugging and monitoring.
* Display user-friendly error messages.
* Offer alternative payment methods.
* Implement automatic retry for transient errors.
* Send error notifications to support teams.
* Track error rates for quality monitoring.


#### Callback signature

```kotlin
onError: ((BaseSdkException) -> Unit)?
```

#### Event data

| Event data | Description |
|  --- | --- |
| `error`BaseSdkException | The SDK exception containing error details. |
| `error.message`String? | A human-readable error description. |
| `error.ErrorCode`String | The error code for programmatic handling. |
| `error.cause`Throwable? | The underlying cause of the error, if available. |


#### Exception types

| Exception | Code | Description |
|  --- | --- | --- |
| `GooglePayNotReadyException` | SDK0701 | Google Pay not available on device. |
| `GooglePayPaymentFailedException` | SDK0702 | Payment processing failed. |
| `GooglePayClientNotInitializedException` | SDK0703 | Client not properly initialised. |
| `GooglePayLoadFailedException` | SDK0704 | Failed to load Google Pay API. |
| `GooglePayConfigurationValidationFailedException` | SDK0705 | Invalid configuration. |
| `GooglePayDecryptAndTokenVaultFailedException` | SDK0706 | Token processing failed. |
| `UnsupportedFundingTypeGooglePaySdkException` | SDK0110 | Google Pay not in allowed funding types. |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onError = { error ->
        Log.e("GooglePay", "Error occurred", error)
        
        // Log error for debugging
        logError("google-pay-error") {
            put("message", error.message ?: "Unknown error")
            put("code", error.ErrorCode)
            put("timestamp", System.currentTimeMillis())
            put("user_id", getCurrentUserId())
        }
        
        // Handle specific error types
        when (error) {
            is GooglePayNotReadyException -> {
                Log.e("GooglePay", "Google Pay not available")
                showError("Google Pay is not available on this device. Please use another payment method.")
                showAlternativePaymentMethods()
                analyticsTracker.trackEvent("google_pay_not_available")
            }
            
            is GooglePayConfigurationValidationFailedException -> {
                Log.e("GooglePay", "Configuration validation failed")
                showError("Payment system temporarily unavailable. Please try again later.")
                notifySupport("Configuration error", error)
            }
            
            is GooglePayClientNotInitializedException -> {
                Log.e("GooglePay", "Client not initialised")
                showError("Payment system not ready. Please refresh and try again.")
                notifySupport("Client initialisation error", error)
            }
            
            is GooglePayLoadFailedException -> {
                Log.e("GooglePay", "Failed to load Google Pay")
                showError("Unable to load payment system. Please check your connection.")
                offerRetryOption()
            }
            
            is GooglePayPaymentFailedException -> {
                Log.e("GooglePay", "Payment processing failed")
                showError("Payment failed. Please check your payment information.")
                offerRetryOption()
            }
            
            is GooglePayDecryptAndTokenVaultFailedException -> {
                Log.e("GooglePay", "Token vault error")
                showError("Payment processing error. Please try again.")
                notifySupport("Token vault error", error)
            }
            
            else -> {
                Log.e("GooglePay", "Unexpected error: ${error.message}")
                showError("An unexpected error occurred. Please try again.")
                analyticsTracker.trackEvent("google_pay_error") {
                    put("error_type", error::class.simpleName ?: "Unknown")
                    put("error_code", error.ErrorCode)
                }
            }
        }
    }
}
```

### onCancel

This callback is triggered when the customer cancels the Google Pay payment flow.

You can use it to:

* Track cancellation rates for conversion optimisation.
* Show helpful messages or alternative options.
* Save the customer's cart for later completion.
* Trigger abandoned cart workflows.
* Understand drop-off points in the payment flow.
* Offer assistance or support.


#### Callback signature

```kotlin
onCancel: (() -> Unit)?
```

#### Event data

This callback receives no parameters.

#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onCancel = {
        Log.d("GooglePay", "Payment cancelled by user")
        
        // Track cancellation
        analyticsTracker.trackEvent("google_pay_cancelled") {
            put("timestamp", System.currentTimeMillis())
            put("cart_value", getCurrentCartValue())
            put("item_count", getCartItemCount())
        }
        
        // Preserve cart for later
        saveCartForLater()
        
        // Show helpful message
        showMessage(
            "No worries! Your items are saved. You can complete your purchase anytime.",
            isError = false
        )
        
        // Offer support for high-value carts
        if (getCartValue() > 100.0) {
            showSupportDialog("Need help completing your purchase?")
        }
        
        // Schedule abandoned cart notification
        lifecycleScope.launch {
            scheduleAbandonedCartNotification(
                customerId = getCurrentCustomerId(),
                cartData = getCurrentCartData(),
                delayMinutes = 60
            )
        }
    }
}
```

## 3D Secure authentication events

### useUnityAuthenticationStrategy

This configuration parameter controls which authentication strategy is used for 3DS authentication.

#### Configuration

```kotlin
useUnityAuthenticationStrategy: Boolean = false
```

When `true`, uses Unity's automatic authentication strategy. When `false` or not specified, uses manual authentication callbacks.

#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // Enable Unity authentication strategy
    useUnityAuthenticationStrategy = true
    
    // Simplified callbacks when using Unity strategy
    onPreInitiateAuthentication = {
        PreInitiateIntegratedAuthenticationData(
            providerId = "pxpfinancial",
            timeout = 12
        )
    }
    
    onPreAuthentication = suspend {
        InitiateIntegratedAuthenticationData(
            merchantCountryNumericCode = "840",
            merchantLegalName = "Your Store Name",
            challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
            requestorChallengeIndicator = RequestorChallengeIndicatorType.NO_PREFERENCE,
            timeout = 180
        )
    }
}
```

### onPreInitiateAuthentication

This callback is triggered before initiating the 3DS pre-authentication check.

You can use it to:

* Configure 3DS provider settings.
* Set authentication request preferences.
* Specify timeout values for authentication requests.
* Control the initial authentication flow.


#### Callback signature

```kotlin
onPreInitiateAuthentication: (() -> PreInitiateIntegratedAuthenticationData?)?
```

#### Event data

This callback receives no parameters.

#### Return value

| Return value | Description |
|  --- | --- |
| `PreInitiateIntegratedAuthenticationData` | Configuration for 3DS pre-authentication. |
| `PreInitiateIntegratedAuthenticationData.providerId` | Your 3DS provider identifier. Optional. |
| `PreInitiateIntegratedAuthenticationData.requestorAuthenticationIndicator` | The authentication indicator. Optional.Possible values:`PAYMENT_TRANSACTION` (01)`RECURRING_TRANSACTION` (02)`INSTALLMENT_TRANSACTION` (03)`ADD_CARD` (04)`MAINTAIN_CARD` (05)`CARD_HOLDER_VERIFICATION` (06)`BILLING_AGREEMENT` (07)`DEVICE_BINDING` (08)`SPLIT_DELAYED_SHIPMENT` (09)`TOP_UP` (10) |
| `PreInitiateIntegratedAuthenticationData.timeout` | The timeout to get the fingerprint result, in seconds (max 600). Optional. |
| `null` | Return null to skip 3DS authentication. |


#### Example implementation

```kotlin
import com.pxp.checkout.services.models.authentication.PreInitiateIntegratedAuthenticationData
import com.pxp.checkout.models.RequestorAuthenticationIndicatorType

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPreInitiateAuthentication = {
        Log.d("GooglePay", "Initiating 3DS pre-authentication")
        
        // Track 3DS initiation
        analyticsTracker.trackEvent("3ds_pre_initiate")
        
        PreInitiateIntegratedAuthenticationData(
            providerId = "your-3ds-provider-id", // Optional
            requestorAuthenticationIndicator = RequestorAuthenticationIndicatorType.PAYMENT_TRANSACTION,
            timeout = 300 // 5 minutes
        )
    }
}
```

### onPostInitiateAuthentication

This callback is triggered after the 3DS pre-authentication check completes.

You can use it to:

* Read `authenticationId` and `threeDSecureSupported` from the success result.
* Retrieve authentication assessment results from your backend using the `authenticationId`.
* Determine if full authentication is required.
* Log authentication initiation for compliance.
* Check SCA requirements and exemptions.
* Decide whether to proceed with challenge.


#### Callback signature

```kotlin
onPostInitiateAuthentication: ((AuthenticationResult) -> Unit)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `result`AuthenticationResult | Authentication initiation result. Success is `MerchantAuthenticationResult`; failure is `FailedAuthenticationResult`. |
| `result.authenticationId`String? | Unique identifier for this 3DS session. May be null when 3DS isn't required. Use this to retrieve full pre-authentication results from your backend. |
| `result.threeDSecureSupported`Boolean | Whether 3DS authentication is supported for this transaction. |


When both `authenticationId` is absent and `threeDSecureSupported` is `false`, the SDK skips the remaining 3DS flow and doesn't invoke `onPreAuthentication`. This early-exit check applies when `useUnityAuthenticationStrategy` is `false`. When `useUnityAuthenticationStrategy` is `true`, the SDK uses Unity's authentication strategy API instead. Use the `authenticationId` with the [Get 3DS pre-initiate authentication details](/apis/three-d-secure-authentication/integrated-authentication/get-preinitiate-authentication-details) API to retrieve SCA mandates, exemptions, and state.

#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPostInitiateAuthentication = { result ->
        when (result) {
            is FailedAuthenticationResult -> {
                Log.e("GooglePay", "3DS pre-initiation failed: ${result.errorReason}")
            }
            is MerchantAuthenticationResult -> {
                Log.d("GooglePay", "Pre-authentication complete: ${result.authenticationId}")
                Log.d("GooglePay", "3DS supported: ${result.threeDSecureSupported}")

                if (result.authenticationId.isNullOrEmpty() && !result.threeDSecureSupported) {
                    return@onPostInitiateAuthentication
                }

                val authId = result.authenticationId ?: return@onPostInitiateAuthentication

                lifecycleScope.launch {
                    try {
                        val authResult = fetchAuthenticationResultFromBackend(authId)

                        if (authResult.state == "PendingClientData") {
                            Log.d("GooglePay", "Waiting for client data collection")
                        }

                        if (authResult.scaMandated) {
                            Log.d("GooglePay", "SCA is required - 3DS must complete successfully")
                            withContext(Dispatchers.Main) {
                                showMessage("Additional verification required")
                            }
                        }

                        evaluateAuthenticationAndUpdateSession(authResult)
                    } catch (e: Exception) {
                        Log.e("GooglePay", "Error retrieving authentication result", e)
                    }
                }
            }
        }
    }
}
```

### onPreAuthentication

This callback is triggered before full 3DS authentication begins, allowing you to provide authentication configuration.

You can use it to:

* Specify merchant details for authentication.
* Configure challenge window preferences.
* Set challenge indicators based on risk assessment.
* Provide country-specific merchant information.
* Control the authentication user experience.


#### Callback signature

```kotlin
onPreAuthentication: (suspend () -> InitiateIntegratedAuthenticationData?)?
```

#### Event data

This callback receives no parameters.

#### Return value

| Return value | Description |
|  --- | --- |
| `InitiateIntegratedAuthenticationData` | Configuration for 3DS authentication. |
| `InitiateIntegratedAuthenticationData.merchantCountryNumericCode` | Your merchant location, as an ISO numeric country code (e.g., "840" for USA, "826" for UK). Optional. |
| `InitiateIntegratedAuthenticationData.merchantLegalName` | Your legal business name. Optional. |
| `InitiateIntegratedAuthenticationData.challengeWindowSize` | The challenge window size. Optional.Possible values:`FULL_SCREEN` (01 - Fullscreen)`IFRAME_250x400` (02 - 250x400 px)`IFRAME_390x400` (03 - 390x400 px)`IFRAME_500x600` (04 - 500x600 px)`IFRAME_600x400` (05 - 600x400 px) |
| `InitiateIntegratedAuthenticationData.requestorChallengeIndicator` | The challenge preference indicator. Optional.Possible values:`NO_PREFERENCE` (01)`NO_CHALLENGE_REQUESTED` (02)`CHALLENGE_REQUESTED_3DS_REQUESTOR_PREFERENCE` (03)`CHALLENGE_REQUESTED_MANDATE` (04)`NO_CHALLENGE_REQUESTED_TRA_PERFORMED` (05)`NO_CHALLENGE_REQUESTED_LOW_VALUE_EXEMPTION` (10) |
| `InitiateIntegratedAuthenticationData.timeout` | Timeout to get the challenge result, in seconds (max 600). Optional. |
| `InitiateIntegratedAuthenticationData.billingAddress` | Billing address for authentication. Optional. |
| `InitiateIntegratedAuthenticationData.shopper` | Shopper information. Optional. |
| `null` | Return null to skip authentication. |


#### Example implementation

```kotlin
import com.pxp.checkout.services.models.authentication.InitiateIntegratedAuthenticationData
import com.pxp.checkout.models.RequestorChallengeIndicatorType
import com.pxp.checkout.components.threeds.ChallengeWindowSize

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPreAuthentication = suspend {
        Log.d("GooglePay", "Configuring 3DS authentication")
        
        // Get authentication decision from backend
        val decision = getAuthenticationDecision()
        
        if (decision == null) {
            Log.d("GooglePay", "Skipping authentication based on backend decision")
            return@suspend null
        }
        
        // Assess transaction risk
        val amount = getTransactionAmount()
        val customerHistory = getCustomerHistory()
        val isHighRisk = amount > 500.0 || customerHistory.chargebacks > 0
        
        // Determine challenge indicator
        val challengeIndicator = when {
            isHighRisk -> RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_MANDATE
            amount < 30.0 && customerHistory.successfulTransactions > 10 -> 
                RequestorChallengeIndicatorType.NO_CHALLENGE_REQUESTED
            else -> RequestorChallengeIndicatorType.NO_PREFERENCE
        }
        
        Log.d("GooglePay", "Challenge indicator: $challengeIndicator")
        
        InitiateIntegratedAuthenticationData(
            merchantCountryNumericCode = "826", // United Kingdom
            merchantLegalName = "Your Store Limited",
            challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
            requestorChallengeIndicator = challengeIndicator,
            timeout = if (decision.scaMandated) 600 else 300,
            billingAddress = getBillingAddress()?.let {
                BillingAddress(
                    city = it.city,
                    countryNumericCode = it.countryCode,
                    line1 = it.streetAddress,
                    postalCode = it.postalCode
                )
            },
            shopper = ShopperData(
                email = getCurrentCustomerEmail(),
                homePhoneNumber = getCurrentCustomerPhone()
            )
        )
    }
}
```

### onPostAuthentication

This callback is triggered after 3DS authentication completes, providing the authentication result.

You can use it to:

* Retrieve authentication results.
* Log authentication outcomes for compliance.
* Track authentication success rates.
* Handle authentication failures gracefully.
* Record ECI values and authentication indicators.


#### Callback signature

```kotlin
onPostAuthentication: ((AuthenticationResult) -> Unit)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`MerchantAuthenticationResult | Authentication result containing the authentication ID. |
| `data.authenticationId`String | Unique identifier for the authentication attempt. Use this to retrieve full authentication details from your backend. |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPostAuthentication = { data ->
        Log.d("GooglePay", "3DS authentication completed: ${data.authenticationId}")
        
        lifecycleScope.launch {
            try {
                // Retrieve authentication result from backend
                val authResult = fetchAuthenticationResultFromBackend(data.authenticationId)
                
                Log.d("GooglePay", "Authentication result: $authResult")
                
                // Check transaction status
                when (authResult.transactionStatus) {
                    "Y" -> {
                        Log.d("GooglePay", "Authentication successful - no challenge needed")
                        showMessage("Card verified successfully")
                    }
                    "C" -> {
                        if (authResult.state == "AuthenticationSuccessful") {
                            Log.d("GooglePay", "Challenge completed successfully")
                            showMessage("Verification completed")
                        } else {
                            Log.e("GooglePay", "Challenge failed")
                            showError("Verification failed. Please try again.")
                            return@launch // Stop payment
                        }
                    }
                    "N" -> {
                        Log.e("GooglePay", "Authentication failed")
                        showError("Card verification failed")
                        return@launch // Stop payment
                    }
                    "R" -> {
                        Log.e("GooglePay", "Authentication rejected")
                        showError("Payment was rejected by your bank")
                        return@launch // Stop payment
                    }
                }
                
                // Evaluate and update authorisation decision
                evaluateAuthenticationAndUpdateAuthorization(authResult)
                
                Log.d("GooglePay", "Proceeding to final authorisation...")
            } catch (e: Exception) {
                Log.e("GooglePay", "Error processing authentication result", e)
                showError("Authentication processing error")
            }
        }
    }
}
```

## Consent and token events

### onGetConsent

This callback is called to determine if the customer has given consent to store their payment token for future use.

The primary use case is to set a default consent value (`true` or `false`) when you're not rendering the `google-pay-consent` checkbox component. This allows you to control token storage behaviour without requiring explicit customer interaction.

When both `googlePayConsentComponent` and `onGetConsent` are provided, the consent component value takes priority. The `onGetConsent` callback will not be called if a consent component is linked.

You can use it to:

* Set a default consent value when not using the consent checkbox component.
* Check custom consent checkbox status from your own UI.
* Verify terms and conditions acceptance for stored payments.
* Control token storage behaviour based on business logic.
* Implement opt-in/opt-out for recurring payments.
* Respect customer privacy preferences.


#### Callback signature

```kotlin
onGetConsent: (() -> Boolean)?
```

#### Event data

This callback receives no parameters.

#### Return value

| Return Type | Description |
|  --- | --- |
| `Boolean` | Return `true` if consent was given, `false` otherwise. |


#### Example implementation

**Example 1: Set default consent value without checkbox**

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... payment configuration
    
    onGetConsent = {
        // Return a default value when not using consent checkbox
        // Set to true to always store tokens, false to never store
        Log.d("GooglePay", "Setting default consent: true")
        true // Always save payment method for this merchant
    }
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                Log.d("GooglePay", "Payment successful, token will be stored")
                // Token storage is handled automatically based on consent
            }
            else -> { /* Handle failure */ }
        }
    }
}
```

**Example 2: Use custom consent checkbox from your UI**

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... payment configuration
    
    onGetConsent = {
        // Check your own custom checkbox
        val consentGiven = customConsentCheckbox.isChecked
        
        if (consentGiven) {
            Log.d("GooglePay", "Customer consented to save payment method")
            
            // Track consent
            analyticsTracker.trackEvent("payment_consent_given") {
                put("timestamp", System.currentTimeMillis())
                put("payment_method", "google-pay")
            }
        } else {
            Log.d("GooglePay", "Customer did not consent to save payment method")
        }
        
        consentGiven
    }
}
```

**Example 3: With consent component (component value takes priority)**

```kotlin
// Create consent component
val consentComponent = pxpCheckout.createComponent<
    GooglePayConsentComponent,
    GooglePayConsentConfig
>(
    type = ComponentType.GOOGLE_PAY_CONSENT,
    config = GooglePayConsentConfig(
        label = "Save this payment method for future purchases",
        initialChecked = false
    )
)

// Create Google Pay button with consent
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... payment configuration
    
    // Link the consent component
    googlePayConsentComponent = consentComponent
    
    // This callback will NOT be called because consent component is linked
    onGetConsent = {
        Log.d("GooglePay", "This callback is ignored when component is linked")
        true // This is ignored when consent component is present
    }
}

// The consent component's checked state will be used for token storage
```

## CVC collection callbacks

The Google Pay Button Component can request CVV/CVC codes for additional security. While there's no specific callback for CVC collection, you can configure the behaviour:

### CVC collection configuration

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // CVC collection strategy
    collectCvc = GooglePayButtonComponentConfig.CvcCollectionStrategy.DEFAULT
    
    // Custom CVC dialog configuration (optional)
    cvcVerificationPopupConfig = CvcVerificationDialogConfig(
        title = "Security Verification",
        description = "Please enter your card's CVV code",
        submitButtonText = "Confirm Payment",
        cancelButtonText = "Go Back",
        
        // Custom styling
        titleStyles = TextStyle(
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold,
            color = Color(0xFF1A237E)
        ),
        
        // CVC input configuration
        cvcComponentConfig = CardCvcComponentConfig().apply {
            label = "CVV"
            showHintIcon = true
            showValidIcon = true
            showMaskToggle = true
        }
    )
}
```

### CVC collection strategies

```kotlin
enum class CvcCollectionStrategy {
    ALWAYS,   // Always show CVC dialog for FPAN
    NEVER,    // Never show CVC dialog
    DEFAULT   // Show CVC only when 3DS not available/skipped
}
```

## Risk screening events

### onKountCollectStart

This callback is triggered when Kount fraud detection data collection begins.

You can use it to:

* Track fraud detection start time.
* Show loading indicators during fraud screening.
* Log fraud detection events.
* Monitor fraud detection performance.


#### Callback signature

```kotlin
onKountCollectStart: ((Any) -> Unit)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`Any | Kount collection start data. |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onKountCollectStart = { data ->
        Log.d("GooglePay", "Kount fraud detection started")
        
        // Track fraud detection start
        analyticsTracker.trackEvent("kount_collection_start") {
            put("timestamp", System.currentTimeMillis())
        }
        
        // Show fraud check indicator (optional)
        lifecycleScope.launch(Dispatchers.Main) {
            showFraudCheckIndicator()
        }
    }
}
```

### onKountCollectEnd

This callback is triggered when Kount fraud detection data collection completes.

You can use it to:

* Track fraud detection completion time.
* Hide loading indicators.
* Log fraud detection completion.
* Calculate fraud detection duration.


#### Callback signature

```kotlin
onKountCollectEnd: ((Any) -> Unit)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`Any | Kount collection completion data. |


#### Example implementation

```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onKountCollectStart = { data ->
        val startTime = System.currentTimeMillis()
        
        // Store start time for duration calculation
        fraudDetectionStartTime = startTime
        
        Log.d("GooglePay", "Kount collection started at $startTime")
    }
    
    onKountCollectEnd = { data ->
        val endTime = System.currentTimeMillis()
        val duration = endTime - fraudDetectionStartTime
        
        Log.d("GooglePay", "Kount collection completed in ${duration}ms")
        
        // Track fraud detection completion
        analyticsTracker.trackEvent("kount_collection_end") {
            put("timestamp", endTime)
            put("duration_ms", duration)
        }
        
        // Hide fraud check indicator
        lifecycleScope.launch(Dispatchers.Main) {
            hideFraudCheckIndicator()
        }
    }
}

private var fraudDetectionStartTime: Long = 0
```

## Soft decline retry events

### onPreRetrySoftDecline

This callback is triggered before retrying a soft-declined payment with different authentication parameters.

You can use it to:

* Configure retry attempts with alternative challenge indicators.
* Implement custom retry logic for soft declines.
* Track soft decline occurrences.
* Adjust authentication parameters for better success rates.
* Decide whether to retry or abort.


#### Callback signature

```kotlin
onPreRetrySoftDecline: ((SubmitResult) -> RetryConfiguration?)?
```

#### Event data

| Parameter | Description |
|  --- | --- |
| `result`SubmitResult | Transaction result containing decline information. |
| `result.errorCode`String | Error code indicating decline type. |
| `result.errorReason`String | Human-readable decline reason. |
| `result.correlationId`String | Correlation ID for tracking. |
| `result.merchantTransactionId`String | Your transaction identifier. |
| `result.systemTransactionId`String? | System transaction identifier (if available). |


#### Return value

| Property | Description |
|  --- | --- |
| `retry`Boolean | Whether to retry the payment. |
| `updatedConfigs`UpdatedConfigs? | Updated callback configurations for retry attempt. Contains optional `onPreInitiateAuthentication`, `onPreAuthentication`, and `onPreAuthorisation` callbacks. |


#### Example implementation

```kotlin
import com.pxp.checkout.components.googlepay.types.GooglePayButtonComponentConfig
import com.pxp.checkout.services.models.authentication.*
import com.pxp.checkout.models.*
import com.pxp.checkout.components.threeds.ChallengeWindowSize

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    onPreRetrySoftDecline = { result ->
        Log.w("GooglePay", "Soft decline detected, preparing retry")
        Log.w("GooglePay", "Error code: ${result.errorCode}")
        Log.w("GooglePay", "Decline reason: ${result.errorReason}")
        Log.w("GooglePay", "Correlation ID: ${result.correlationId}")
        
        // Track soft decline
        analyticsTracker.trackEvent("payment_soft_decline") {
            put("error_code", result.errorCode)
            put("error_reason", result.errorReason)
            put("correlation_id", result.correlationId)
            put("transaction_id", result.merchantTransactionId)
        }
        
        // Show message to user
        showMessage("Processing payment with additional verification...")
        
        // Retry with forced 3DS challenge
        GooglePayButtonComponentConfig.RetryConfiguration(
            retry = true,
            updatedConfigs = GooglePayButtonComponentConfig.RetryConfiguration.UpdatedConfigs(
                onPreInitiateAuthentication = {
                    PreInitiateIntegratedAuthenticationData(
                        providerId = "pxpfinancial",
                        timeout = 12
                    )
                },
                onPreAuthentication = suspend {
                    InitiateIntegratedAuthenticationData(
                        merchantCountryNumericCode = "826",
                        merchantLegalName = "Your Store Ltd",
                        challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
                        requestorChallengeIndicator = 
                            RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_MANDATE,
                        timeout = 300
                    )
                }
            )
        )
        
        // Alternative: Don't retry
        // GooglePayButtonComponentConfig.RetryConfiguration(retry = false)
    }
}
```

## Complete implementation example

Here's a comprehensive example showing multiple callbacks working together:

```kotlin
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.pxp.PxpCheckout
import com.pxp.checkout.components.googlepay.*
import com.pxp.checkout.components.googlepay.types.*
import com.pxp.checkout.models.*
import com.pxp.checkout.services.kount.*
import com.pxp.checkout.types.ComponentType
import kotlinx.coroutines.CompletableDeferred

@Composable
fun GooglePayPaymentScreen(
    pxpCheckout: PxpCheckout,
    amount: Double,
    onSuccess: (String) -> Unit,
    onError: (String) -> Unit
) {
    var errorMessage by remember { mutableStateOf<String?>(null) }
    var isProcessing by remember { mutableStateOf(false) }
    
    val googlePayComponent = remember {
        pxpCheckout.createComponent<
            GooglePayButtonComponent,
            GooglePayButtonComponentConfig
        >(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                
                // Payment configuration
                style = GooglePayButtonStyle(
                    type = GooglePayButtonType.PAY,
                    theme = GooglePayButtonTheme.DARK,
                    cornerRadius = 8
                )
                
                paymentDataRequest = PaymentDataRequest(
                    allowedPaymentMethods = listOf(
                        PaymentMethodSpecification(
                            parameters = PaymentMethodParameters(
                                allowedAuthMethods = listOf(
                                    CardAuthMethod.PAN_ONLY,
                                    CardAuthMethod.CRYPTOGRAM_3DS
                                ),
                                allowedCardNetworks = listOf(
                                    CardNetwork.VISA,
                                    CardNetwork.MASTERCARD,
                                    CardNetwork.AMEX
                                )
                            )
                        )
                    ),
                    transactionInfo = TransactionInfo(
                        currencyCode = "GBP",
                        totalPriceStatus = TotalPriceStatus.FINAL,
                        totalPrice = amount.toString(),
                        countryCode = "GB"
                    ),
                    emailRequired = true
                )
                
                // Enable 3DS with Unity strategy
                useUnityAuthenticationStrategy = true
                collectCvc = GooglePayButtonComponentConfig.CvcCollectionStrategy.DEFAULT
                
                // Button click callback
                onGooglePaymentButtonClicked = {
                    Log.d("GooglePay", "Button clicked")
                    isProcessing = true
                    errorMessage = null
                    CompletableDeferred(Unit)
                }
                
                // Pre-authorisation callback
                onPreAuthorisation = suspend { data ->
                    Log.d("GooglePay", "Pre-authorisation: ${data?.gatewayTokenId}")
                    
                    GooglePayTransactionInitData(
                        riskScreeningData = RiskScreeningData(
                            performRiskScreening = true,
                            excludeDeviceData = false,
                            userIp = "192.168.1.100",
                            account = RiskScreeningAccount(
                                id = "user_12345678",
                                creationDateTime = "2024-01-15T10:30:00.000Z"
                            ),
                            fulfillments = listOf(
                                RiskScreeningFulfillment(
                                    type = FulfillmentType.SHIPPED,
                                    recipientPerson = RiskScreeningRecipientPerson(
                                        phoneNumber = "+1234567890"
                                    )
                                )
                            )
                        )
                    )
                }
                
                // Post-authorisation callback
                onPostAuthorisation = { result, paymentData ->
                    isProcessing = false
                    
                    when (result) {
                        is MerchantSubmitResult -> {
                            Log.d("GooglePay", "Payment successful")
                            onSuccess(result.merchantTransactionId)
                        }
                        is FailedSubmitResult -> {
                            Log.e("GooglePay", "Payment failed: ${result.errorReason}")
                            errorMessage = result.errorReason
                            onError(result.errorReason)
                        }
                    }
                }
                
                // Error callback
                onError = { error ->
                    Log.e("GooglePay", "Error: ${error.message}", error)
                    isProcessing = false
                    errorMessage = error.message
                    onError(error.message ?: "Payment error")
                }
                
                // Cancel callback
                onCancel = {
                    Log.d("GooglePay", "Payment cancelled")
                    isProcessing = false
                    errorMessage = "Payment cancelled"
                }
            }
        )
    }
    
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Complete Your Payment",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Text(
            text = "£${amount}",
            style = MaterialTheme.typography.displaySmall
        )
        
        Spacer(modifier = Modifier.height(24.dp))
        
        // Render Google Pay button
        googlePayComponent?.Content(
            modifier = Modifier.fillMaxWidth()
        )
        
        // Error message
        errorMessage?.let { error ->
            Spacer(modifier = Modifier.height(16.dp))
            Text(
                text = error,
                color = MaterialTheme.colorScheme.error,
                style = MaterialTheme.typography.bodyMedium
            )
        }
        
        // Processing indicator
        if (isProcessing) {
            Spacer(modifier = Modifier.height(16.dp))
            CircularProgressIndicator()
        }
    }
}
```

## Analytics events

The SDK automatically tracks key events throughout the Google Pay payment flow. These analytics events help you monitor payment performance, identify issues, and optimise the user experience.

### Automatic event tracking

The following events are tracked automatically without any additional configuration:

| Event | Description |
|  --- | --- |
| `GooglePayButtonClickAnalyticsEvent` | Triggered when the user taps the Google Pay button. Helps track button engagement and conversion funnel entry. |
| `GooglePayReadinessCheckAnalyticsEvent` | Triggered when the SDK checks if Google Pay is available on the device. Useful for understanding device compatibility. |
| `GooglePaySheetOpenedAnalyticsEvent` | Triggered when the Google Pay payment sheet opens. Indicates successful button interaction and sheet loading. |
| `GooglePaySheetCompletedAnalyticsEvent` | Triggered when the Google Pay payment sheet closes (either completed or cancelled). Helps track sheet completion rates. |
| `GooglePayTokenReceivedAnalyticsEvent` | Triggered when a payment token is successfully received from Google Pay. Indicates successful tokenisation. |
| `GooglePayFlowCancelledAnalyticsEvent` | Triggered when the user cancels the payment flow. Helps identify drop-off points and cancellation patterns. |
| `ComponentErrorAnalyticsEvent` | Triggered when an error occurs during the payment flow. Essential for monitoring error rates and types. |


### Event data structure

Each analytics event contains:

* **Event name:** The type of event that occurred.
* **Timestamp:** When the event was triggered.
* **Component type:** Always `GOOGLE_PAY_BUTTON` for Google Pay events.
* **Additional context:** Event-specific data (e.g., error details, token information).


### Accessing analytics data

Analytics events are automatically sent to the PXP backend and can be viewed in the Unity Portal analytics dashboard. You can use this data to:

* **Monitor conversion rates:** Track how many users complete payments after clicking the button.
* **Identify technical issues:** Detect patterns in error events and readiness check failures.
* **Optimise user experience:** Understand where users drop off in the payment flow.
* **Track performance metrics:** Measure average time from button click to payment completion.


### Custom analytics integration

If you want to integrate with your own analytics platform (e.g., Firebase Analytics, Google Analytics), you can track events using the callback functions:

```kotlin
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase

val analytics = Firebase.analytics

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    
    onGooglePaymentButtonClicked = {
        // Track button click
        analytics.logEvent("google_pay_button_clicked", Bundle().apply {
            putLong("timestamp", System.currentTimeMillis())
            putString("currency", "GBP")
            putDouble("amount", 99.99)
        })
        CompletableDeferred(Unit)
    }
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                // Track successful payment
                analytics.logEvent("google_pay_payment_success", Bundle().apply {
                    putString("transaction_id", result.merchantTransactionId)
                    putString("system_transaction_id", result.systemTransactionId)
                })
            }
            is FailedSubmitResult -> {
                // Track failed payment
                analytics.logEvent("google_pay_payment_failed", Bundle().apply {
                    putString("error_code", result.errorCode)
                    putString("error_reason", result.errorReason)
                    putString("correlation_id", result.correlationId)
                })
            }
        }
    }
    
    onError = { error ->
        // Track errors
        analytics.logEvent("google_pay_error", Bundle().apply {
            putString("error_type", error::class.simpleName)
            putString("error_message", error.message)
        })
    }
    
    onCancel = {
        // Track cancellations
        analytics.logEvent("google_pay_cancelled", Bundle().apply {
            putLong("timestamp", System.currentTimeMillis())
        })
    }
}
```