Skip to content

Non-3DS transactions

Process Google Pay transactions without 3D Secure authentication for faster, streamlined checkout.

Overview

By implementing a non-3DS Google Pay payment flow on Android, you benefit from:

  • Faster checkout: Streamlined payment process with fewer steps and no authentication redirects.
  • Better conversion: Reduced friction leads to higher completion rates.
  • Simplified integration: Fewer callbacks and less complex implementation.
  • Lower latency: Direct payment processing without authentication steps.
  • Native security: Benefits from Google Pay's tokenisation and biometric authentication.

However, non-3DS payments may not qualify for liability shift protection and are best suited for low-risk transactions, trusted customers, or regions where Strong Customer Authentication (SCA) isn't mandated.

Google Pay provides inherent security through device-based authentication (fingerprint, face recognition, or PIN) and payment tokenisation, making non-3DS flows suitable for many transaction types.

Payment flow

The non-3DS Google Pay payment flow consists of five streamlined 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: Evaluation

The SDK evaluates whether 3DS authentication is required. In non-3DS flows, authentication is skipped based on:

  • Configuration not requiring 3DS (no onPreInitiateAuthentication callback provided).
  • Transaction falling below risk thresholds.
  • Regulatory exemptions being applicable.
  • Merchant risk assessment.

Since 3DS isn't required, the flow proceeds directly to authorisation.

Step 5: Authorisation

The SDK sends the authorisation request with the Google Pay token directly to the payment gateway without 3DS authentication data.

Associated callbacks

  • onPreAuthorisation: Provides transaction data for final review before authorisation. This is your last opportunity to add additional data or cancel the transaction.
  • onPostAuthorisation: Receives the final transaction result. The transaction is either authorised or declined.

Implementation

Before you start

To use non-3DS Google Pay payments on Android:

  1. Ensure your merchant account supports non-3DS transactions.
  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. Consider implementing additional fraud prevention measures.

Step 1: Initialise the SDK

Initialise the PXP Checkout SDK with your merchant credentials:

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

val pxpCheckout = PxpCheckout.builder()
    .withConfig(
        PxpSdkConfig(
            environment = Environment.TEST,
            session = sessionData,
            ownerId = "your-owner-id",
            ownerType = "MerchantGroup",
            transactionData = TransactionData(
                currency = "GBP",
                amount = 25.00,
                merchantTransactionId = UUID.randomUUID().toString(),
                merchantTransactionDate = { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(Date()) },
                entryType = EntryType.Ecom,
                intent = TransactionIntentData(
                    card = IntentType.Authorisation
                ),
                merchant = "Merchant Name"
            )
        )
    )
    .withContext(context)
    .build()

Step 2: Implement callbacks

Configure the Google Pay button with the required callbacks for non-3DS payments:

import com.pxp.checkout.components.googlepay.*
import com.pxp.checkout.components.googlepay.types.*
import com.pxp.checkout.models.*
import com.pxp.checkout.services.kount.*

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD,
                        CardNetwork.AMEX
                    ),
                    // Both methods supported - Google Pay will use best available
                    allowedAuthMethods = listOf(
                        CardAuthMethod.PAN_ONLY,
                        CardAuthMethod.CRYPTOGRAM_3DS
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(
            currencyCode = "GBP",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = "49.99"
        )
    )
    
    // REQUIRED: Final transaction review before authorisation
    onPreAuthorisation = suspend { data ->
        Log.d("GooglePay", "Processing non-3DS payment")
        Log.d("GooglePay", "Gateway Token ID: ${data?.gatewayTokenId}")
        
        // Add risk screening data
        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 = 99.99,
                        quantity = 1,
                        category = "Electronics",
                        sku = "GPAY-PROD-001"
                    )
                ),
                fulfillments = listOf(
                    RiskScreeningFulfillment(
                        type = FulfillmentType.SHIPPED,
                        shipping = RiskScreeningShipping(
                            shippingMethod = ShippingMethod.STANDARD
                        ),
                        recipientPerson = RiskScreeningRecipientPerson(
                            phoneNumber = "+1234567890",
                            email = "customer@example.com"
                        )
                    )
                )
            )
        )
    }
    
    // REQUIRED: Handle the final result
    onPostAuthorisation = { result, paymentData ->
        Log.d("GooglePay", "Payment result: $result")
        
        when (result) {
            is MerchantSubmitResult -> {
                // Success
                Log.d("GooglePay", "Payment successful!")
                Log.d("GooglePay", "Transaction ID: ${result.merchantTransactionId}")
                Log.d("GooglePay", "System Txn ID: ${result.systemTransactionId}")
                
                // Navigate to success screen
                navigateToSuccess(result.merchantTransactionId)
            }
            is FailedSubmitResult -> {
                // Failure
                Log.e("GooglePay", "Payment declined: ${result.errorReason}")
                showError("Payment declined. Please try another payment method.")
            }
        }
    }
    
    // OPTIONAL: Handle errors
    onError = { error ->
        Log.e("GooglePay", "Payment error", error)
        showError("An error occurred. Please try again.")
    }
    
    // OPTIONAL: Handle cancellation
    onCancel = {
        Log.d("GooglePay", "Payment cancelled by user")
        showMessage("Payment cancelled")
    }
}

Step 3: Handle common scenarios

Amount-based risk assessment

Apply different risk measures based on transaction amounts:

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPreAuthorisation = suspend { data ->
            Log.d("GooglePay", "Gateway Token ID: ${data?.gatewayTokenId}")
            
            // Get transaction amount
            val amount = getTransactionAmount().toDoubleOrNull() ?: 0.0
            
            // Different handling based on amount
                when {
                amount < 30.0 -> {
                    // Low-value transaction - minimal checks
                    Log.d("GooglePay", "Low-value transaction: $amount")
                    GooglePayTransactionInitData(
                        riskScreeningData = RiskScreeningData(
                            performRiskScreening = false,
                            excludeDeviceData = true
                        )
                    )
                }
                amount < 100.0 -> {
                    // Medium-value transaction - standard checks
                    Log.d("GooglePay", "Medium-value transaction: $amount")
                    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,
                                    shipping = RiskScreeningShipping(
                                        shippingMethod = ShippingMethod.STANDARD
                                    ),
                                    recipientPerson = RiskScreeningRecipientPerson(
                                        phoneNumber = "+1234567890"
                                    )
                                )
                            )
                        )
                    )
                }
                else -> {
                    // High-value transaction - enhanced checks
                    Log.d("GooglePay", "High-value transaction: $amount")
                    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 = amount,
                                    quantity = 1,
                                    category = "high-value"
                                )
                            ),
                            fulfillments = listOf(
                                RiskScreeningFulfillment(
                                    type = FulfillmentType.SHIPPED,
                                    shipping = RiskScreeningShipping(
                                        shippingMethod = ShippingMethod.EXPRESS
                                    ),
                                    recipientPerson = RiskScreeningRecipientPerson(
                                        phoneNumber = "+1234567890"
                                    )
                                )
                            )
                        )
                    )
                }
        }
    }
}

Customer type handling

Handle different customer types with varying risk profiles:

data class CustomerRiskProfile(
    val level: RiskLevel,
    val verification: VerificationLevel,
    val reason: String
)

enum class RiskLevel { HIGH, MEDIUM, LOW }
enum class VerificationLevel { ENHANCED, STANDARD, MINIMAL }

fun getCustomerRiskProfile(customerId: String): CustomerRiskProfile {
    val customer = getCustomerDetails(customerId)
    
    return when {
        customer.previousOrders == 0 -> {
            CustomerRiskProfile(
                level = RiskLevel.HIGH,
                verification = VerificationLevel.ENHANCED,
                reason = "new-customer"
            )
        }
        customer.previousOrders > 10 && customer.chargebacks == 0 -> {
            CustomerRiskProfile(
                level = RiskLevel.LOW,
                verification = VerificationLevel.MINIMAL,
                reason = "trusted-customer"
            )
        }
        else -> {
            CustomerRiskProfile(
                level = RiskLevel.MEDIUM,
                verification = VerificationLevel.STANDARD,
                reason = "returning-customer"
            )
        }
    }
}

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPreAuthorisation = suspend { data ->
            val customerId = getCurrentCustomerId()
            val riskProfile = getCustomerRiskProfile(customerId)
            
            Log.d("GooglePay", "Customer risk profile: $riskProfile")
            
            GooglePayTransactionInitData(
                riskScreeningData = RiskScreeningData(
                    performRiskScreening = riskProfile.level != RiskLevel.LOW,
                    excludeDeviceData = false,
                    userIp = "192.168.1.100",
                    account = RiskScreeningAccount(
                        id = customerId,
                        creationDateTime = "2024-01-15T10:30:00.000Z"
                    ),
                    fulfillments = listOf(
                        RiskScreeningFulfillment(
                            type = FulfillmentType.SHIPPED,
                            recipientPerson = RiskScreeningRecipientPerson(
                                phoneNumber = "+1234567890"
                            )
                        )
                    )
                )
            )
    }
}

Geographic risk assessment

Apply different rules based on customer location:

suspend fun getCustomerGeolocation(): Geolocation {
    // Implement geolocation detection
    return Geolocation(
        countryCode = "GB",
        region = "England",
        city = "London"
    )
}

data class Geolocation(
    val countryCode: String,
    val region: String,
    val city: String
)

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPreAuthorisation = suspend { data ->
            Log.d("GooglePay", "Gateway Token ID: ${data?.gatewayTokenId}")
            
            val geolocation = getCustomerGeolocation()
            
            // Check if customer is in high-risk region
            val highRiskCountries = listOf("XX", "YY", "ZZ")
            val isHighRisk = geolocation.countryCode in highRiskCountries
            
            Log.d("GooglePay", "Customer location: ${geolocation.countryCode}, High risk: $isHighRisk")
            
            GooglePayTransactionInitData(
                riskScreeningData = RiskScreeningData(
                    performRiskScreening = true,
                    excludeDeviceData = false,
                    userIp = geolocation.ipAddress,
                    account = RiskScreeningAccount(
                        id = "user_12345678",
                        creationDateTime = "2024-01-15T10:30:00.000Z"
                    ),
                    fulfillments = listOf(
                        RiskScreeningFulfillment(
                            type = FulfillmentType.SHIPPED,
                            recipientPerson = RiskScreeningRecipientPerson(
                                phoneNumber = "+1234567890"
                            )
                        )
                    )
                )
            )
    }
}

Step 4: Handle errors

Implement comprehensive error handling for non-3DS payments:

val googlePayConfig = GooglePayButtonComponentConfig().apply {
    // ... basic configuration
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                // Payment successful
                handlePaymentSuccess(result, paymentData)
            }
            is FailedSubmitResult -> {
                // Payment declined or failed
                handlePaymentFailure(result)
            }
        }
    }
    
    onError = { error ->
        Log.e("GooglePay", "Payment error", error)
        
        // Handle specific error types
        val errorMessage = when (error) {
            is GooglePayNotReadyException -> {
                "Google Pay is not available on this device. Please try another payment method."
            }
            is GooglePayConfigurationValidationFailedException -> {
                logCriticalError("Google Pay configuration error", error)
                "Payment system configuration error. Please contact support."
            }
            is GooglePayLoadFailedException -> {
                "Failed to load Google Pay. Please try again."
            }
            is GooglePayPaymentFailedException -> {
                "Payment failed. Please try another payment method."
            }
            else -> {
                "An error occurred. Please try again or use a different payment method."
            }
        }
        
        showError(errorMessage)
    }
    
    onCancel = {
        Log.d("GooglePay", "Payment cancelled by user")
        // Customer closed Google Pay sheet
        showMessage("Payment cancelled. Your order is still in your cart.")
    }
}

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

fun handlePaymentFailure(result: FailedSubmitResult) {
    Log.e("GooglePay", "Payment failed")
    Log.e("GooglePay", "Error code: ${result.errorCode}")
    Log.e("GooglePay", "Error reason: ${result.errorReason}")
    Log.e("GooglePay", "Correlation ID: ${result.correlationId}")
    
    // Show user-friendly error message
    showError("Payment declined. Please try another payment method.")
    
    // Log decline for analysis
    logPaymentDecline(
        PaymentDeclineLog(
            errorCode = result.errorCode,
            errorReason = result.errorReason,
            correlationId = result.correlationId,
            httpStatusCode = result.httpStatusCode,
            timestamp = System.currentTimeMillis()
        )
    )
    
    // Offer alternative payment methods
    showAlternativePaymentOptions()
}

data class TransactionRecord(
    val transactionId: String,
    val systemTransactionId: String,
    val processingType: String,
    val paymentMethod: String,
    val timestamp: Long
)

data class PaymentDeclineLog(
    val errorCode: String,
    val errorReason: String,
    val correlationId: String,
    val httpStatusCode: Int,
    val timestamp: Long
)

Complete example

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

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.services.kount.*
import com.pxp.checkout.types.ComponentType
import kotlinx.coroutines.CompletableDeferred
import java.util.*

@Composable
fun GooglePayCheckout(
    pxpCheckout: PxpCheckout,
    amount: String,
    onSuccess: (String) -> Unit,
    onError: (String) -> Unit
) {
    var isProcessing by remember { mutableStateOf(false) }
    var errorMessage by remember { mutableStateOf<String?>(null) }
    
    // Create Google Pay button (non-3DS)
    val googlePayComponent = remember {
        pxpCheckout.createComponent<
            GooglePayButtonComponent,
            GooglePayButtonComponentConfig
        >(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                
                style = GooglePayButtonStyle(
                    type = GooglePayButtonType.PAY,
                    theme = GooglePayButtonTheme.DARK,
                    cornerRadius = 8
                )
                
                paymentDataRequest = PaymentDataRequest(
                    allowedPaymentMethods = listOf(
                        PaymentMethodSpecification(
                            parameters = PaymentMethodParameters(
                                allowedCardNetworks = listOf(
                                    CardNetwork.VISA,
                                    CardNetwork.MASTERCARD,
                                    CardNetwork.AMEX
                                ),
                                allowedAuthMethods = listOf(
                                    CardAuthMethod.PAN_ONLY,
                                    CardAuthMethod.CRYPTOGRAM_3DS
                                )
                            )
                        )
                    ),
                    transactionInfo = TransactionInfo(
                        currencyCode = "GBP",
                        countryCode = "GB",
                        totalPriceStatus = TotalPriceStatus.FINAL,
                        totalPrice = amount,
                        totalPriceLabel = "Total"
                    )
                )
                
                // Handle button click
                onGooglePaymentButtonClicked = {
                    Log.d("GooglePay", "Button clicked")
                    isProcessing = true
                    errorMessage = null
                    CompletableDeferred(Unit)
                }
                
                // Pre-authorisation - add risk data
                onPreAuthorisation = suspend { data ->
                    Log.d("GooglePay", "Processing non-3DS payment")
                    Log.d("GooglePay", "Gateway Token ID: ${data?.gatewayTokenId}")
                    
                    // Assess risk
                    val customerId = getCurrentCustomerId()
                    val riskProfile = assessCustomerRisk(customerId)
                    
                    GooglePayTransactionInitData(
                        riskScreeningData = RiskScreeningData(
                            performRiskScreening = true,
                            excludeDeviceData = false,
                            userIp = "192.168.1.100",
                            account = RiskScreeningAccount(
                                id = customerId,
                                creationDateTime = "2024-01-15T10:30:00.000Z"
                            ),
                            items = listOf(
                                RiskScreeningItem(
                                    price = 99.99,
                                    quantity = 1,
                                    category = "Electronics"
                                )
                            ),
                            fulfillments = listOf(
                                RiskScreeningFulfillment(
                                    type = FulfillmentType.SHIPPED,
                                    shipping = RiskScreeningShipping(
                                        shippingMethod = ShippingMethod.STANDARD
                                    ),
                                    recipientPerson = RiskScreeningRecipientPerson(
                                        phoneNumber = "+1234567890"
                                    )
                                )
                            )
                        )
                    )
                }
                
                // Post-authorisation - handle result
                onPostAuthorisation = { result, _ ->
                    Log.d("GooglePay", "Payment result received")
                    isProcessing = false
                    
                    when (result) {
                        is MerchantSubmitResult -> {
                            Log.d("GooglePay", "Payment authorised")
                            
                            // Track successful payment
                            trackPaymentSuccess(
                                transactionId = result.merchantTransactionId,
                                systemTransactionId = result.systemTransactionId,
                                paymentMethod = "google-pay"
                            )
                            
                            onSuccess(result.merchantTransactionId)
                        }
                        is FailedSubmitResult -> {
                            Log.e("GooglePay", "Payment declined: ${result.errorReason}")
                            errorMessage = "Payment declined. Please try another payment method."
                            
                            // Track decline
                            trackPaymentDecline(
                                errorCode = result.errorCode,
                                errorReason = result.errorReason,
                                correlationId = result.correlationId
                            )
                        }
                    }
                }
                
                // Error handling
                onError = { error ->
                    Log.e("GooglePay", "Payment error", error)
                    isProcessing = false
                    errorMessage = "An error occurred. Please try again."
                    
                    // Track error
                    trackPaymentError(
                        errorType = error::class.simpleName ?: "Unknown",
                        errorMessage = error.message ?: "Unknown error"
                    )
                }
                
                // Cancellation handling
                onCancel = {
                    Log.d("GooglePay", "Payment cancelled by user")
                    isProcessing = false
                    errorMessage = null
                    
                    // Track cancellation
                    trackPaymentCancellation()
                }
            }
        )
    }
    
    // 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 Headphones")
                        Text("£45.00")
                    }
                    
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text("VAT (20%)")
                        Text("£4.99")
                    }
                    
                    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",
                        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) {
                        Spacer(modifier = Modifier.height(16.dp))
                        
                        LinearProgressIndicator(
                            modifier = Modifier.fillMaxWidth()
                        )
                        
                        Text(
                            text = "Processing 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 = "✓ Fast and secure checkout with Google Pay",
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                        Text(
                            text = "✓ No need to enter card details",
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                        Text(
                            text = "✓ Protected by Google Pay's security",
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                    }
                }
            }
        }
    }
}

// Helper functions
private fun getCurrentCustomerId(): String {
    // Get customer ID from your auth system
    return "customer-123"
}

private suspend fun assessCustomerRisk(customerId: String): String {
    // Implement your risk assessment logic
    val orderHistory = getCustomerOrderHistory(customerId)
    
    return when {
        orderHistory.totalOrders > 10 && orderHistory.chargebacks == 0 -> "low"
        orderHistory.totalOrders == 0 -> "high"
        else -> "medium"
    }
}

private fun trackPaymentSuccess(
    transactionId: String,
    systemTransactionId: String,
    paymentMethod: String
) {
    Log.d("Analytics", "Tracking payment success: $transactionId")
    // Implement your analytics tracking
}

private fun trackPaymentDecline(
    errorCode: String,
    errorReason: String,
    correlationId: String
) {
    Log.d("Analytics", "Tracking payment decline: $errorCode")
    // Implement your analytics tracking
}

private fun trackPaymentError(errorType: String, errorMessage: String) {
    Log.d("Analytics", "Tracking payment error: $errorType")
    // Implement your analytics tracking
}

private fun trackPaymentCancellation() {
    Log.d("Analytics", "Tracking payment cancellation")
    // Implement your analytics tracking
}

Callback data

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

onPreAuthorisation

The onPreAuthorisation callback receives pre-authorisation data containing token identifiers. Use this to retrieve token details and make authorisation decisions.

Event data

ParameterDescription
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).

Use the gatewayTokenId with the Get masked card data related to gateway token API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.

Example implementation

onPreAuthorisation = suspend { data ->
    Log.d("GooglePay", "Non-3DS authorisation for token: ${data?.gatewayTokenId}")
    
    // Retrieve token details and make authorisation decision
    val transactionDecision = getAuthorisationDecision(data?.gatewayTokenId)
    
    if (transactionDecision == null) {
        Log.d("GooglePay", "Not proceeding with authorisation")
        null
    } else {
        // Add risk screening data
        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"
                        )
                    )
                )
            )
        )
    }
}

3DS external data should no longer be provided via the callback. For non-3DS flows, no 3DS data is applicable.

onPostAuthorisation

The onPostAuthorisation callback receives the authorisation result and the original payment data from Google Pay.

Event data

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

Success result

When the payment is successful, you'll receive a MerchantSubmitResult:

data class MerchantSubmitResult(
    val merchantTransactionId: String,
    val systemTransactionId: String
)
PropertyTypeDescription
merchantTransactionIdStringThe merchant's unique identifier for the transaction that was provided during SDK initialisation.
systemTransactionIdStringThe system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from the Unity backend.

Failure result

When the payment fails, you'll receive a FailedSubmitResult:

data class FailedSubmitResult(
    val errorCode: String,
    val errorReason: String,
    val correlationId: String,
    val httpStatusCode: Int
)
PropertyTypeDescription
errorCodeStringThe error code indicating the type of failure.
errorReasonStringA human-readable description of the error.
correlationIdStringA unique identifier for this transaction attempt. Use this for support inquiries and debugging.
httpStatusCodeIntThe HTTP status code associated with the failure.

Example implementation

onPostAuthorisation = { result, paymentData ->
    Log.d("GooglePay", "Non-3DS Google Pay payment result")
    
    when (result) {
        is MerchantSubmitResult -> {
            // Success
            Log.d("GooglePay", "✅ Payment successful!")
            Log.d("GooglePay", "Merchant Transaction ID: ${result.merchantTransactionId}")
            Log.d("GooglePay", "System Transaction ID: ${result.systemTransactionId}")
            
            // Store transaction details
            storeTransactionRecord(
                TransactionRecord(
                    merchantTransactionId = result.merchantTransactionId,
                    systemTransactionId = result.systemTransactionId,
                    paymentMethod = "google-pay",
                    processingType = "non-3ds",
                    timestamp = System.currentTimeMillis()
                )
            )
            
            // Redirect to success page
            navigateToSuccess(result.merchantTransactionId)
        }
        is FailedSubmitResult -> {
            // Failure
            Log.e("GooglePay", "❌ Payment failed")
            Log.e("GooglePay", "Error code: ${result.errorCode}")
            Log.e("GooglePay", "Error reason: ${result.errorReason}")
            Log.e("GooglePay", "Correlation ID: ${result.correlationId}")
            
            // Handle payment failure
            handlePaymentFailure(result)
        }
    }
}

onError

The onError callback is triggered when an error occurs during the Google Pay flow (before reaching authorisation).

Event data

ParameterDescription
error
BaseSdkException
required
The exception that occurred during the payment flow.

Example implementation

onError = { error ->
    Log.e("GooglePay", "Google Pay error", error)
    
    // Handle specific error types
    val errorMessage = when (error) {
        is GooglePayNotReadyException -> {
            "Google Pay is not available on this device. Please try another payment method."
        }
        is GooglePayConfigurationValidationFailedException -> {
            logCriticalError("Google Pay configuration error", error)
            "Payment system configuration error. Please contact support."
        }
        is GooglePayLoadFailedException -> {
            "Failed to load Google Pay. Please try again."
        }
        is GooglePayPaymentFailedException -> {
            "Payment failed. Please try another payment method."
        }
        else -> {
            "An error occurred. Please try again or use a different payment method."
        }
    }
    
    showError(errorMessage)
    
    // Re-enable payment options
    hideLoadingSpinner()
    enablePaymentOptions()
}

onCancel

The onCancel callback is triggered when the customer closes the Google Pay payment sheet without completing the payment.

Event data

This callback does not receive any parameters.

Example implementation

onCancel = {
    Log.d("GooglePay", "Google Pay payment cancelled by user")
    
    // Track cancellation for analytics
    trackEvent("payment-cancelled", mapOf(
        "paymentMethod" to "google-pay",
        "timestamp" to System.currentTimeMillis()
    ))
    
    // Show message to user
    showMessage("Payment cancelled. Your order is still in your cart.")
    
    // Reset UI state
    hideLoadingSpinner()
    enablePaymentOptions()
}