# 3DS payments

Learn about 3D Secure authentication with Google Pay for enhanced transaction security.

## Overview

By implementing 3DS authentication with Google Pay on Android, you benefit from:

* **Enhanced security**: Additional authentication layer protects against fraudulent transactions.
* **Liability shift**: Shifts liability from merchant to card issuer for authenticated transactions.
* **Reduced chargebacks**: Significantly decreases chargeback rates through strong authentication.
* **Regulatory compliance**: Meets Strong Customer Authentication (SCA) requirements under PSD2 and similar regulations.
* **Higher success rate**: Banks are more likely to approve 3DS-authenticated transactions.
* **Fraud prevention**: Real-time risk assessment and challenge when necessary.


However, the 3DS payment flow is longer than the non-3DS one due to the additional authentication steps. It may also require active customer participation if a challenge is presented.

3D Secure authentication is strongly recommended for high-value transactions, subscription services, and any scenario where enhanced security is required. It significantly reduces fraud risk and chargebacks whilst providing liability shift benefits.

## Payment flow

The 3DS Google Pay payment flow consists of nine key steps.

### Step 1: Button interaction

The customer taps the Google Pay button in your Android app, triggering the payment sheet to open. Google Pay handles device authentication (biometric or PIN) internally.

### Step 2: Payment method selection

Within the Google Pay payment sheet, the customer selects their preferred payment method. Google Pay tokenises the payment data using its secure tokenisation system.

### Step 3: Payment sheet completion

The customer confirms the payment in the Google Pay sheet. The SDK receives the encrypted payment token from Google Pay.

### Step 4: Pre-initiation

This is the initial step in the 3DS authentication flow. It establishes the authentication session by sending transaction and card details to the payment processor.

This step has two associated callbacks:

* **`onPreInitiateAuthentication`**: Returns configuration for the authentication setup.
* **`onPostInitiateAuthentication`**: Receives the result of the pre-initiation call.


### Step 5: Fingerprinting

During this step, device information and characteristics are collected by the fingerprinting component. The SDK gathers device data and submits transaction details to the issuer's fingerprint URL, enabling risk assessment based on the user's device profile.

### Step 6: Authentication

The 3DS server evaluates the transaction risk and determines the authentication path:

* **Frictionless flow:** If the transaction is low-risk, authentication completes automatically without customer interaction
* **Challenge flow:** If additional verification is needed, the customer completes the 3DS authentication challenge in a native Android dialog or WebView (PIN entry, SMS code, biometric verification, etc.)


The authentication step has two associated callbacks:

* **`onPreAuthentication`**: Configures the main authentication parameters.
* **`onPostAuthentication`**: Receives authentication results and challenge data.


### Step 7: Authentication result

The SDK receives the 3DS authentication result indicating whether authentication was successful, failed, or requires additional action.

### Step 8: Authorisation

This is the final processing step where the SDK sends the authorisation request to the payment gateway, including the 3DS authentication data. You receive the transaction data along with the 3DS authentication results and decide whether to proceed.

The authorisation step has two associated callbacks:

* **`onPreAuthorisation`**: Provides final transaction data, including 3DS authentication results. This is your last chance to modify the transaction before authorisation.
* **`onPostAuthorisation`**: Receives the final transaction result from the payment gateway. The transaction is either approved or declined and final transaction details are available, along with 3DS authentication confirmation.


### Step 9: Authorisation result

You receive the final authorisation response from the payment gateway. The transaction is either approved or declined and final transaction details are available, along with 3DS authentication confirmation.

## Implementation

### Before you start

To use 3DS with Google Pay payments on Android:

1. Ensure 3D Secure is enabled in the Unity Portal for your merchant group.
2. Configure Google Pay in the Unity Portal with your gateway merchant ID.
3. Register your Android app package name and certificate in the Unity Portal.
4. Get your 3DS provider ID from your payment processor.
5. Ensure your app has the required permissions in `AndroidManifest.xml`.


### Step 1: Initialise the SDK

Initialise the PXP Checkout SDK with your merchant credentials:


```kotlin
import com.pxp.PxpCheckout
import com.pxp.checkout.models.*
import java.text.SimpleDateFormat
import java.util.*

class PaymentActivity : ComponentActivity() {
    
    private lateinit var pxpCheckout: PxpCheckout
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Fetch session from your secure backend
        val sessionConfig = fetchSessionFromBackend()
        
        // Initialise SDK
        pxpCheckout = PxpCheckout.builder()
            .withConfig(
                PxpSdkConfig(
                    environment = Environment.TEST,
                    session = sessionConfig,
                    transactionData = TransactionData(
                        currency = "GBP",
                        amount = 299.99,
                        merchant = "YOUR_MERCHANT_ID",
                        merchantTransactionId = UUID.randomUUID().toString(),
                        merchantTransactionDate = {
                            SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
                                timeZone = TimeZone.getTimeZone("UTC")
                            }.format(Date())
                        },
                        entryType = EntryType.Ecom,
                        intent = TransactionIntentData(
                            card = IntentType.Authorisation
                        )
                    ),
                    ownerType = "MerchantGroup",
                    ownerId = "your-owner-id"
                )
            )
            .withContext(this)
            .build()
    }
}
```

### Step 2: Implement callbacks

Configure the Google Pay button with the required callbacks for 3DS authentication:


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

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    
    // Payment request configuration
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD,
                        CardNetwork.AMEX
                    ),
                    // Prefer 3DS cryptogram authentication
                    allowedAuthMethods = listOf(
                        CardAuthMethod.CRYPTOGRAM_3DS
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(
            currencyCode = "GBP",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = "299.99"
        )
    )
    
    // RECOMMENDED: Use Unity authentication strategy
    useUnityAuthenticationStrategy = true
    
    // REQUIRED: Provide 3DS pre-initiation configuration
    onPreInitiateAuthentication = {
        PreInitiateIntegratedAuthenticationData(
            providerId = "your_3ds_provider_id", // Optional
            requestorAuthenticationIndicator = RequestorAuthenticationIndicatorType.PAYMENT_TRANSACTION,
            timeout = 300 // 5 minutes in seconds
        )
    }
    
    // OPTIONAL: Handle the pre-initiation result
    onPostInitiateAuthentication = { data ->
        Log.d("GooglePay3DS", "Pre-initiation complete: ${data.authenticationId}")
        
        // Note: This callback is not suspend, so use coroutine scope if needed
        // Use lifecycleScope from your Activity or ViewModel context
    }
    
    // REQUIRED: Configure main authentication
    onPreAuthentication = suspend {
        // This is a suspend function - no need for lifecycleScope.async
        // Get authentication decision from backend
        val decision = getAuthenticationDecision()
        
        if (decision == null) {
            // Not proceeding with authentication
            null
        } else {
            InitiateIntegratedAuthenticationData(
                merchantCountryNumericCode = "826", // United Kingdom
                merchantLegalName = "Your Store Limited",
                challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
                requestorChallengeIndicator = decision.challengeIndicator 
                    ?: RequestorChallengeIndicatorType.NO_PREFERENCE,
                timeout = decision.timeout ?: 300
            )
        }
    }
    
    // OPTIONAL: Handle the authentication result
    onPostAuthentication = { data ->
        Log.d("GooglePay3DS", "Authentication complete: ${data.authenticationId}")
        
        // Note: This callback is not suspend, so use coroutine scope if needed
        // Use lifecycleScope from your Activity or ViewModel context
    }
    
    // REQUIRED: Final transaction review before authorisation
    onPreAuthorisation = suspend { data ->
        // This is a suspend function - no need for lifecycleScope.async
        Log.d("GooglePay3DS", "Processing payment. Token: ${data?.gatewayTokenId}")
        
        // Get authorisation decision from backend
        val transactionDecision = getAuthorisationDecision(data?.gatewayTokenId)
        
        if (transactionDecision == null) {
            null
        } else {
            // Return transaction data with risk screening
            GooglePayTransactionInitData(
                riskScreeningData = RiskScreeningData(
                    performRiskScreening = true,
                    excludeDeviceData = false,
                    userIp = "192.168.1.100",
                    account = RiskScreeningAccount(
                        id = "user_12345678",
                        creationDateTime = "2024-01-15T10:30:00.000Z"
                    ),
                    items = listOf(
                        RiskScreeningItem(
                            price = 299.99,
                            quantity = 1,
                            category = "Electronics",
                            sku = "GPAY-PROD-001"
                        )
                    ),
                    fulfillments = listOf(
                        RiskScreeningFulfillment(
                            type = FulfillmentType.SHIPPED,
                            shipping = RiskScreeningShipping(
                                shippingMethod = ShippingMethod.EXPRESS
                            ),
                            recipientPerson = RiskScreeningRecipientPerson(
                                phoneNumber = "+1234567890",
                                email = "customer@example.com"
                            )
                        )
                    )
                )
            )
        }
    }
    
    // REQUIRED: Handle the final result
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                // Success
                Log.d("GooglePay3DS", "Payment successful!")
                Log.d("GooglePay3DS", "Merchant Txn: ${result.merchantTransactionId}")
                Log.d("GooglePay3DS", "System Txn: ${result.systemTransactionId}")
                
                // Navigate to success screen
                navigateToSuccess(result.merchantTransactionId)
            }
            is FailedSubmitResult -> {
                // Failure
                Log.e("GooglePay3DS", "Payment failed: ${result.errorReason}")
                showError(result.errorReason)
            }
        }
    }
    
    // OPTIONAL: Handle errors
    onError = { error ->
        Log.e("GooglePay3DS", "Error: ${error.message}", error)
        showError("Payment error. Please try again.")
    }
    
    // OPTIONAL: Handle cancellation
    onCancel = {
        Log.d("GooglePay3DS", "Payment cancelled by user")
        showMessage("Payment cancelled")
    }
}
```

### Step 3: Handle common scenarios

#### Conditional 3DS

Only trigger 3DS authentication above a certain amount:


```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPreInitiateAuthentication = {
        val amount = getTransactionAmount()
        
        if (amount > 100.0) {
            PreInitiateIntegratedAuthenticationData(
                providerId = "your_provider",
                requestorAuthenticationIndicator = RequestorAuthenticationIndicatorType.PAYMENT_TRANSACTION,
                timeout = 300
            )
        } else {
            // Return null to skip 3DS for low-value transactions
            null
        }
    }
}
```

#### Challenge indicator selection

Select appropriate challenge indicators based on transaction risk:


```kotlin
fun selectChallengeIndicator(transaction: Transaction): RequestorChallengeIndicatorType {
    return when {
        // High-value transactions
        transaction.amount > 500.0 -> {
            RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_MANDATE
        }
        
        // Subscription first payment
        transaction.type == TransactionType.SUBSCRIPTION && transaction.isFirstPayment -> {
            RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_3DS_REQUESTOR_PREFERENCE
        }
        
        // Trusted returning customers
        transaction.customerTrust == CustomerTrust.HIGH -> {
            RequestorChallengeIndicatorType.NO_CHALLENGE_REQUESTED
        }
        
        // Default
        else -> RequestorChallengeIndicatorType.NO_PREFERENCE
    }
}

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPreAuthentication = suspend {
        val decision = getAuthenticationDecision()
        
        if (decision == null) {
            null
        } else {
            val transaction = getCurrentTransaction()
            val challengeIndicator = selectChallengeIndicator(transaction)
            
            InitiateIntegratedAuthenticationData(
                merchantCountryNumericCode = "826",
                merchantLegalName = "Your Store Ltd",
                challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
                requestorChallengeIndicator = challengeIndicator
            )
        }
    }
}
```

#### Soft decline retry

Handle soft declines by retrying with forced 3DS challenge:


```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... other configuration
    
    onPreRetrySoftDecline = { result ->
        Log.d("GooglePay3DS", "Soft decline detected, retrying with 3DS challenge")
        
        // Access result details based on result type
        when (result) {
            is RefusedSubmitResult -> {
                // Track soft decline
                trackEvent("payment-soft-decline", mapOf(
                    "stateCode" to result.stateData.code,
                    "stateMessage" to result.stateData.message,
                    "providerCode" to (result.providerResponse?.code ?: "")
                ))
            }
            else -> {
                Log.w("GooglePay3DS", "Unexpected result type for soft decline: ${result::class.simpleName}")
            }
        }
        
        // Retry with forced 3DS challenge
        GooglePayButtonComponentConfig.RetryConfiguration(
            retry = true,
            updatedConfigs = GooglePayButtonComponentConfig.RetryConfiguration.UpdatedConfigs(
                onPreInitiateAuthentication = {
                    PreInitiateIntegratedAuthenticationData(
                        providerId = "your_3ds_provider_id",
                        requestorAuthenticationIndicator = RequestorAuthenticationIndicatorType.PAYMENT_TRANSACTION,
                        timeout = 300
                    )
                },
                onPreAuthentication = suspend {
                    InitiateIntegratedAuthenticationData(
                        merchantCountryNumericCode = "826",
                        merchantLegalName = "Your Store Ltd",
                        challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
                        // Force challenge on retry
                        requestorChallengeIndicator = RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_MANDATE
                    )
                }
            )
        )
    }
}
```

### Step 4: Handle errors

Implement comprehensive error handling for 3DS payments:


```kotlin
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPostAuthentication = { data ->
        // Note: This is a non-suspend callback
        // Use lifecycleScope from your Activity or ViewModel context if you need to call suspend functions
        
        Log.d("GooglePay3DS", "Authentication complete: ${data.authenticationId}")
        // Process authentication result or trigger backend calls
    }
    
    onError = { error ->
        Log.e("GooglePay3DS", "Payment error", error)
        
        // Handle error
        val errorMessage = "Payment failed. Please try again or use a different payment method."
        showError(errorMessage)
    }
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                handlePaymentSuccess(result, paymentData)
            }
            is FailedSubmitResult -> {
                handlePaymentFailure(result)
            }
        }
    }
}

fun handlePaymentSuccess(result: MerchantSubmitResult, paymentData: AuthorisationPaymentData) {
    Log.d("GooglePay3DS", "✅ Payment successful with 3DS")
    Log.d("GooglePay3DS", "Transaction ID: ${result.merchantTransactionId}")
    Log.d("GooglePay3DS", "System Transaction ID: ${result.systemTransactionId}")
    
    // Store transaction details
    storeTransactionRecord(
        transactionId = result.merchantTransactionId,
        systemTransactionId = result.systemTransactionId,
        processingType = "3ds",
        paymentMethod = "google-pay",
        timestamp = System.currentTimeMillis()
    )
    
    // Navigate to success page
    navigateToSuccess(result.merchantTransactionId)
}

fun handlePaymentFailure(result: FailedSubmitResult) {
    Log.e("GooglePay3DS", "❌ Payment failed")
    Log.e("GooglePay3DS", "Error code: ${result.errorCode}")
    Log.e("GooglePay3DS", "Error reason: ${result.errorReason}")
    Log.e("GooglePay3DS", "Correlation ID: ${result.correlationId}")
    
    showError("Payment failed: ${result.errorReason}")
    
    // Log for analysis
    logPaymentDecline(
        errorCode = result.errorCode,
        errorReason = result.errorReason,
        correlationId = result.correlationId,
        httpStatusCode = result.httpStatusCode,
        timestamp = System.currentTimeMillis()
    )
    
    // Offer alternative payment methods
    showAlternativePaymentOptions()
}
```

### Complete example

The following example shows a complete 3DS Google Pay implementation with Jetpack Compose:


```kotlin
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.components.threeds.ChallengeWindowSize
import com.pxp.checkout.models.*
import com.pxp.checkout.services.kount.*
import com.pxp.checkout.services.models.authentication.*
import com.pxp.checkout.types.ComponentType
import kotlinx.coroutines.launch
import java.util.*

@Composable
fun GooglePay3DSCheckout(
    pxpCheckout: PxpCheckout,
    amount: String,
    onSuccess: (String) -> Unit,
    onError: (String) -> Unit
) {
    var isProcessing by remember { mutableStateOf(false) }
    var errorMessage by remember { mutableStateOf<String?>(null) }
    val coroutineScope = rememberCoroutineScope()
    
    // Create Google Pay button component with 3DS
    val googlePayComponent = remember {
        pxpCheckout.createComponent<
            GooglePayButtonComponent,
            GooglePayButtonComponentConfig
        >(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                
                paymentDataRequest = PaymentDataRequest(
                    allowedPaymentMethods = listOf(
                        PaymentMethodSpecification(
                            parameters = PaymentMethodParameters(
                                allowedCardNetworks = listOf(
                                    CardNetwork.VISA,
                                    CardNetwork.MASTERCARD,
                                    CardNetwork.AMEX
                                ),
                                allowedAuthMethods = listOf(
                                    CardAuthMethod.CRYPTOGRAM_3DS
                                )
                            )
                        )
                    ),
                    transactionInfo = TransactionInfo(
                        currencyCode = "GBP",
                        countryCode = "GB",
                        totalPriceStatus = TotalPriceStatus.FINAL,
                        totalPrice = amount
                    )
                )
                
                // Use Unity authentication strategy
                useUnityAuthenticationStrategy = true
                
                // Step 1: Set up 3DS
                onPreInitiateAuthentication = {
                    Log.d("GooglePay3DS", "🔐 Initiating 3DS authentication")
                    PreInitiateIntegratedAuthenticationData(
                        providerId = BuildConfig.THREEDS_PROVIDER_ID,
                        requestorAuthenticationIndicator = RequestorAuthenticationIndicatorType.PAYMENT_TRANSACTION,
                        timeout = 300
                    )
                }
                
                // Step 2: Handle pre-initiation result
                onPostInitiateAuthentication = { data ->
                    Log.d("GooglePay3DS", "📋 Pre-authentication check: ${data.authenticationId}")
                    
                    coroutineScope.launch {
                        val authResult = fetchAuthenticationResultFromBackend(data.authenticationId)
                        evaluateAuthenticationAndUpdateSession(authResult)
                        
                        if (authResult.scaMandated) {
                            Log.d("GooglePay3DS", "SCA mandated - full authentication required")
                        }
                    }
                }
                
                // Step 3: Configure authentication
                onPreAuthentication = suspend {
                    Log.d("GooglePay3DS", "🔍 Configuring full 3DS authentication")
                    
                    val decision = getAuthenticationDecision()
                    
                    if (decision == null) {
                        null // Skip authentication
                    } else {
                        // Assess transaction risk
                        val transactionAmount = amount.toDoubleOrNull() ?: 0.0
                        val customerHistory = getCustomerHistory()
                        val isHighRisk = transactionAmount > 500.0 || 
                            customerHistory.chargebacks > 0
                        
                        val challengeIndicator = when {
                            isHighRisk -> RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_MANDATE
                            transactionAmount < 30.0 && 
                                customerHistory.successfulTransactions > 10 -> {
                                RequestorChallengeIndicatorType.NO_CHALLENGE_REQUESTED
                            }
                            else -> RequestorChallengeIndicatorType.NO_PREFERENCE
                        }
                        
                        InitiateIntegratedAuthenticationData(
                            merchantCountryNumericCode = "826",
                            merchantLegalName = "Your Store Limited",
                            challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
                            requestorChallengeIndicator = challengeIndicator
                        )
                    }
                }
                
                // Step 4: Handle authentication result
                onPostAuthentication = { data ->
                    Log.d("GooglePay3DS", "✅ 3DS authentication completed: ${data.authenticationId}")
                    
                    coroutineScope.launch {
                        val authResult = fetchAuthenticationResultFromBackend(data.authenticationId)
                        
                        if (authResult.state == AuthenticationState.AUTHENTICATION_FAILED) {
                            errorMessage = "Card verification failed. Please try again."
                            return@launch
                        }
                        
                        evaluateAuthenticationAndUpdateAuthorization(authResult)
                    }
                }
                
                // Step 5: Final authorisation
                onPreAuthorisation = suspend { data ->
                    Log.d("GooglePay3DS", "💳 Processing payment with 3DS data")
                    
                    val transactionDecision = getAuthorisationDecision(data?.gatewayTokenId)
                    
                    if (transactionDecision == null) {
                        null
                    } else {
                        GooglePayTransactionInitData(
                            riskScreeningData = RiskScreeningData(
                                performRiskScreening = true,
                                excludeDeviceData = false,
                                userIp = "192.168.1.100",
                                account = RiskScreeningAccount(
                                    id = "user_12345678",
                                    creationDateTime = "2024-01-15T10:30:00.000Z"
                                ),
                                items = listOf(
                                    RiskScreeningItem(
                                        price = 299.99,
                                        quantity = 1,
                                        category = "Electronics",
                                        sku = "GPAY-PROD-001"
                                    )
                                ),
                                fulfillments = listOf(
                                    RiskScreeningFulfillment(
                                        type = FulfillmentType.SHIPPED,
                                        shipping = RiskScreeningShipping(
                                            shippingMethod = ShippingMethod.EXPRESS
                                        ),
                                        recipientPerson = RiskScreeningRecipientPerson(
                                            phoneNumber = "+1234567890",
                                            email = "customer@example.com"
                                        )
                                    )
                                )
                            )
                        )
                    }
                }
                
                // Step 6: Handle success/failure
                onPostAuthorisation = { result, _ ->
                    isProcessing = false
                    
                    when (result) {
                        is MerchantSubmitResult -> {
                            Log.d("GooglePay3DS", "✅ Payment authorised with 3DS")
                            onSuccess(result.merchantTransactionId)
                        }
                        is FailedSubmitResult -> {
                            Log.e("GooglePay3DS", "❌ Payment declined: ${result.errorReason}")
                            errorMessage = "Payment declined. Please try another payment method."
                        }
                    }
                }
                
                // Step 7: Error handling
                onError = { error ->
                    Log.e("GooglePay3DS", "Error during 3DS flow", error)
                    isProcessing = false
                    errorMessage = "An error occurred. Please try again."
                }
                
                onCancel = {
                    Log.d("GooglePay3DS", "Payment cancelled by user")
                    isProcessing = false
                    errorMessage = null
                }
            }
        )
    }
    
    // UI
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            // Order summary
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(bottom = 24.dp)
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Text(
                        text = "Order summary",
                        style = MaterialTheme.typography.titleLarge
                    )
                    
                    Spacer(modifier = Modifier.height(16.dp))
                    
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text("Premium Product")
                        Text("£$amount")
                    }
                    
                    Divider(modifier = Modifier.padding(vertical = 8.dp))
                    
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text(
                            text = "Total",
                            style = MaterialTheme.typography.titleMedium
                        )
                        Text(
                            text = "£$amount",
                            style = MaterialTheme.typography.titleMedium
                        )
                    }
                }
            }
            
            // Payment section
            Card(modifier = Modifier.fillMaxWidth()) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Text(
                        text = "Pay with Google Pay (3DS Secure)",
                        style = MaterialTheme.typography.titleLarge
                    )
                    
                    Spacer(modifier = Modifier.height(16.dp))
                    
                    // Error message
                    errorMessage?.let { error ->
                        Card(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(bottom = 16.dp),
                            colors = CardDefaults.cardColors(
                                containerColor = MaterialTheme.colorScheme.errorContainer
                            )
                        ) {
                            Text(
                                text = error,
                                modifier = Modifier.padding(12.dp),
                                color = MaterialTheme.colorScheme.onErrorContainer
                            )
                        }
                    }
                    
                    // Google Pay button
                    googlePayComponent?.Content(
                        modifier = Modifier
                            .fillMaxWidth()
                            .height(56.dp)
                    )
                    
                    // Processing indicator
                    if (isProcessing) {
                        LinearProgressIndicator(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(top = 16.dp)
                        )
                        
                        Text(
                            text = "Processing secure payment...",
                            modifier = Modifier.padding(top = 8.dp),
                            style = MaterialTheme.typography.bodyMedium,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                    }
                    
                    Spacer(modifier = Modifier.height(16.dp))
                    
                    // Security info
                    Column(
                        modifier = Modifier.padding(vertical = 8.dp),
                        verticalArrangement = Arrangement.spacedBy(4.dp)
                    ) {
                        Text(
                            text = "✓ Protected by 3D Secure authentication",
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                        Text(
                            text = "✓ Fast and secure checkout with Google Pay",
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                        Text(
                            text = "✓ Additional verification for your safety",
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                    }
                }
            }
        }
    }
}
```

## Callback data

This section describes the data received by the different callbacks as part of the 3DS Google Pay flow on Android.

The `onPreInitiateAuthentication` callback doesn't receive any parameters. Instead, it returns your 3DS configuration in the `PreInitiateIntegratedAuthenticationData` object.

### onPostInitiateAuthentication

The `onPostInitiateAuthentication` callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`AuthenticationResult | Authentication initiation result. |
| `data.authenticationId`String | The unique identifier for this 3DS session. Use this ID to retrieve full PreInitiateAuthentication result from the Unity backend. |


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 pre-authentication results including SCA mandates, exemptions, and 3DS support.

#### Example implementation


```kotlin
// Within an Activity
onPostInitiateAuthentication = { data ->
    Log.d("GooglePay3DS", "3DS pre-initiation completed: ${data.authenticationId}")
    
    // Use lifecycleScope in Activity context
    lifecycleScope.launch {
        // Retrieve initiate authentication result from Unity
        val authResult = fetchAuthenticationResultFromBackend(data.authenticationId)
        
        // Check if authentication setup was successful
        when (authResult.state) {
            "PendingClientData" -> {
                Log.d("GooglePay3DS", "Waiting for client data collection")
            }
            else -> {
                Log.d("GooglePay3DS", "Authentication state: ${authResult.state}")
            }
        }
        
        // Check if SCA (Strong Customer Authentication) is mandated
        if (authResult.scaMandated) {
            Log.d("GooglePay3DS", "SCA is required - 3DS must complete successfully")
            showMessage("Additional verification required")
        }
        
        // Check available exemptions
        when (authResult.applicableExemptions) {
            "LVP" -> {
                Log.d("GooglePay3DS", "Low value exemption available")
            }
            "TRA" -> {
                Log.d("GooglePay3DS", "Transaction risk analysis exemption available")
            }
        }
        
        // Evaluate the result to update authentication decision
        evaluateAuthenticationAndUpdateSession(authResult)
    }
}
```

### onPreAuthentication

The `onPreAuthentication` callback receives no parameters. Return authentication configuration or null to skip.

#### Example implementation


```kotlin
onPreAuthentication = suspend {
    // Get authentication decision evaluated after onPostInitiateAuthentication
    val decision = getAuthenticationDecision()
    
    if (decision == null) {
        // Not proceeding with authentication
        Log.d("GooglePay3DS", "Skipping authentication based on backend decision")
        null
    } else {
        Log.d("GooglePay3DS", "Proceeding with authentication")
        
        // Check if SCA is mandated to adjust challenge preference
        val challengeIndicator = when {
            decision.scaMandated -> {
                Log.d("GooglePay3DS", "SCA mandated - requesting challenge")
                RequestorChallengeIndicatorType.CHALLENGE_REQUESTED_MANDATE
            }
            decision.applicableExemptions == "LVP" -> {
                Log.d("GooglePay3DS", "Low value exemption - requesting no challenge")
                RequestorChallengeIndicatorType.NO_CHALLENGE_REQUESTED_LOW_VALUE_EXEMPTION
            }
            decision.applicableExemptions == "TRA" -> {
                Log.d("GooglePay3DS", "TRA exemption - requesting no challenge")
                RequestorChallengeIndicatorType.NO_CHALLENGE_REQUESTED_TRANSACTION_RISK_ANALYSIS
            }
            else -> RequestorChallengeIndicatorType.NO_PREFERENCE
        }
        
        InitiateIntegratedAuthenticationData(
            merchantCountryNumericCode = "826",
            merchantLegalName = "Your Company Ltd",
            challengeWindowSize = ChallengeWindowSize.FULL_SCREEN,
            requestorChallengeIndicator = challengeIndicator,
            timeout = if (decision.scaMandated) 600 else 300
        )
    }
}
```

#### Challenge window sizes

| Value | Dimensions | Description |
|  --- | --- | --- |
| `FULL_SCREEN` | Fullscreen | Full-screen window (recommended for mobile) |
| `IFRAME_250x400` | 250x400 px | Compact window |
| `IFRAME_390x400` | 390x400 px | Standard window |
| `IFRAME_500x600` | 500x600 px | Medium window |
| `IFRAME_600x400` | 600x400 px | Wide window |


#### Challenge indicators

| Value | Description | When to use |
|  --- | --- | --- |
| `NO_PREFERENCE` (01) | No preference | Standard transactions |
| `NO_CHALLENGE_REQUESTED` (02) | No challenge requested | Low-risk transactions |
| `CHALLENGE_REQUESTED_3DS_REQUESTOR_PREFERENCE` (03) | Challenge requested: Your preference | When additional security desired |
| `CHALLENGE_REQUESTED_MANDATE` (04) | Challenge requested: Mandate | High-value or high-risk transactions |
| `NO_CHALLENGE_REQUESTED_TRA_PERFORMED` (05) | No challenge requested (transaction risk analysis performed) | When you have performed risk analysis |
| `NO_CHALLENGE_REQUESTED_LOW_VALUE_EXEMPTION` (10) | No challenge requested (Low value exemption) | Low-value transactions under exemption thresholds |


### onPostAuthentication

The `onPostAuthentication` callback receives only the authentication identifier.

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`AuthenticationResult | Authentication result containing authentication details. |
| `data.authenticationId`String | The unique identifier for the authentication attempt. Use to retrieve full details from the Unity backend. |


#### Example implementation


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

### onPreAuthorisation

The `onPreAuthorisation` callback receives only the gateway token ID.

#### 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. |


#### Example implementation


```kotlin
onPreAuthorisation = suspend { data ->
    Log.d("GooglePay3DS", "Pre-authorisation for token: ${data?.gatewayTokenId}")
    
    // Retrieve token details and make authorisation decision
    val transactionDecision = getAuthorisationDecision(data?.gatewayTokenId)
    
    if (transactionDecision == null) {
        Log.d("GooglePay3DS", "Not proceeding with authorisation")
        null
    } else {
        // Perform pre-payment validation
        val isHighRisk = checkCustomerRiskProfile()
        val customerTier = getCustomerTier()
        
        GooglePayTransactionInitData(
            riskScreeningData = RiskScreeningData(
                performRiskScreening = true,
                excludeDeviceData = false,
                userIp = "192.168.1.100",
                account = RiskScreeningAccount(
                    id = "user_12345678",
                    creationDateTime = "2024-01-15T10:30:00.000Z"
                ),
                items = listOf(
                    RiskScreeningItem(
                        price = 299.99,
                        quantity = 1,
                        category = "Electronics",
                        sku = "GPAY-PROD-001"
                    )
                ),
                fulfillments = listOf(
                    RiskScreeningFulfillment(
                        type = FulfillmentType.SHIPPED,
                        shipping = RiskScreeningShipping(
                            shippingMethod = ShippingMethod.EXPRESS
                        ),
                        recipientPerson = RiskScreeningRecipientPerson(
                            phoneNumber = "+1234567890",
                            email = "customer@example.com"
                        )
                    )
                )
            ),
            psd2Data = GooglePayTransactionInitData.Psd2Data(
                scaExemption = if (cartTotal < 30.0) {
                    ScaExemptionType.LOW_VALUE
                } else {
                    ScaExemptionType.TRANSACTION_RISK_ANALYSIS
                }
            )
        )
    }
}
```

### onPostAuthorisation

The `onPostAuthorisation` callback receives the authorisation result and original payment data.

#### Event data

| Parameter | Description |
|  --- | --- |
| `result`SubmitResult? | Transaction result - either `MerchantSubmitResult` (success) or `FailedSubmitResult` (failure). |
| `paymentData`AuthorisationPaymentData | Google Pay payment data collected during checkout (email, shipping address, shipping option). |


#### Success result


```kotlin
data class MerchantSubmitResult(
    val merchantTransactionId: String,
    val systemTransactionId: String
)
```

#### Failure result


```kotlin
data class FailedSubmitResult(
    val errorCode: String,
    val errorReason: String,
    val correlationId: String,
    val httpStatusCode: Int
)
```

#### Example implementation


```kotlin
onPostAuthorisation = { result, paymentData ->
    Log.d("GooglePay3DS", "3DS Google Pay payment result")
    
    when (result) {
        is MerchantSubmitResult -> {
            // Success
            Log.d("GooglePay3DS", "Payment successful with 3DS!")
            Log.d("GooglePay3DS", "Merchant Txn: ${result.merchantTransactionId}")
            Log.d("GooglePay3DS", "System Txn: ${result.systemTransactionId}")
            
            // Store transaction details
            storeTransactionRecord(
                merchantTransactionId = result.merchantTransactionId,
                systemTransactionId = result.systemTransactionId,
                paymentMethod = "google-pay",
                processingType = "3ds",
                liabilityShift = true,
                timestamp = System.currentTimeMillis()
            )
            
            // Navigate to success page
            navigateToSuccess(result.merchantTransactionId)
        }
        is FailedSubmitResult -> {
            // Failure
            Log.e("GooglePay3DS", "Payment failed")
            Log.e("GooglePay3DS", "Error code: ${result.errorCode}")
            Log.e("GooglePay3DS", "Error reason: ${result.errorReason}")
            Log.e("GooglePay3DS", "Correlation ID: ${result.correlationId}")
            
            // Handle specific error types
            handlePaymentFailure(result)
        }
    }
}
```