Skip to content

Configuration

Learn how to configure the Google Pay button component for Android with Kotlin and Jetpack Compose.

Basic usage

Minimal configuration

At minimum, the Google Pay button component requires the following configuration to function:

The SDK automatically configures tokenisation with the correct gateway and merchant ID from your session. You only need to provide allowedPaymentMethods and transactionInfo.

import com.pxp.checkout.components.googlepay.types.*

val config = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD
                    ),
                    allowedAuthMethods = listOf(
                        CardAuthMethod.PAN_ONLY,
                        CardAuthMethod.CRYPTOGRAM_3DS
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(
            currencyCode = "USD",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = "99.99"
        )
    )
}
Property Description
paymentDataRequest
PaymentDataRequest
required
The Google Pay payment request configuration containing payment methods and transaction details.
paymentDataRequest.allowedPaymentMethods
List<PaymentMethodSpecification>
required
List of supported payment methods. At least one payment method is required.
paymentDataRequest.allowedPaymentMethods[].parameters
PaymentMethodParameters
required
Payment method parameters including card networks and authentication methods.
paymentDataRequest.allowedPaymentMethods[].parameters.allowedCardNetworks
List<CardNetwork>
required
Supported card networks.

Possible values:
  • VISA
  • MASTERCARD
  • AMEX
  • DISCOVER
  • JCB
  • INTERAC
paymentDataRequest.allowedPaymentMethods[].parameters.allowedAuthMethods
List<CardAuthMethod>
required
Supported authentication methods.

Possible values:
  • PAN_ONLY: Unencrypted card details
  • CRYPTOGRAM_3DS: 3DS cryptogram authentication
paymentDataRequest.transactionInfo
TransactionInfo
required
Details about the transaction, including the amount and currency.
paymentDataRequest.transactionInfo.currencyCode
String
required
The currency code, in ISO 4217 format (e.g., USD, GBP, EUR).
paymentDataRequest.transactionInfo.totalPriceStatus
TotalPriceStatus
required
The price finality status.

Possible values:
  • FINAL: Price is final
  • ESTIMATED: Price is estimated
paymentDataRequest.transactionInfo.totalPrice
String
required
The total monetary value of the transaction as a string (e.g., 99.99).

Advanced configuration

For more complex implementations, you can configure additional settings and features:

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

val config = GooglePayButtonComponentConfig().apply {
    // Payment details
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD,
                        CardNetwork.AMEX
                    ),
                    allowedAuthMethods = listOf(
                        CardAuthMethod.PAN_ONLY,
                        CardAuthMethod.CRYPTOGRAM_3DS
                    ),
                    billingAddressRequired = true,
                    billingAddressParameters = BillingAddressParameters(
                        format = BillingAddressFormat.FULL,
                        phoneNumberRequired = true
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(
            currencyCode = "USD",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = "99.99",
            countryCode = "US"
        ),
        emailRequired = true,
        shippingAddressRequired = false
    )
    
    // Button appearance
    style = GooglePayButtonStyle(
        type = GooglePayButtonType.BUY,
        theme = GooglePayButtonTheme.DARK,
        cornerRadius = 8
    )
    
    // Enable 3DS authentication
    useUnityAuthenticationStrategy = true
    
    // CVC collection
    collectCvc = GooglePayButtonComponentConfig.CvcCollectionStrategy.DEFAULT
    
    // Event callbacks
    onPreAuthorisation = suspend { 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"
                        )
                    )
                )
            )
        )
    }
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> handleSuccess(result)
            is FailedSubmitResult -> handleFailure(result)
        }
    }
    
    onError = { error ->
        Log.e("GooglePay", "Error: ${error.message}")
    }
}

Styling

Button types and themes

The Google Pay button can be customised to match your app's design:

style = GooglePayButtonStyle(
    type = GooglePayButtonType.CHECKOUT,  // Button text
    theme = GooglePayButtonTheme.DARK,    // Color theme
    cornerRadius = 12                     // Corner radius in dp
)

Button types

enum class GooglePayButtonType {
    BUY,        // "Buy with G Pay"
    PAY,        // "Pay with G Pay" (default)
    CHECKOUT,   // "Checkout with G Pay"
    BOOK,       // "Book with G Pay"
    DONATE,     // "Donate with G Pay"
    ORDER,      // "Order with G Pay"
    SUBSCRIBE,  // "Subscribe with G Pay"
    PLAIN       // Google Pay logo only
}

Themes

enum class GooglePayButtonTheme {
    DARK,   // Black background with white text
    LIGHT   // White background with black text
}

Button sizing

Control button size using standard Compose modifiers:

googlePayComponent?.Content(
    modifier = Modifier
        .fillMaxWidth()    // Full width
        .height(48.dp)     // Fixed height
)

Event callbacks

Configure callbacks to handle payment events. See Events for detailed documentation.

Essential callbacks

val config = GooglePayButtonComponentConfig().apply {
    // Before payment authorisation
    onPreAuthorisation = suspend { 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"
                        )
                    )
                )
            )
        )
    }
    
    // After payment authorisation
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                Log.d("GooglePay", "Success: ${result.merchantTransactionId}")
                navigateToSuccess()
            }
            is FailedSubmitResult -> {
                Log.e("GooglePay", "Failed: ${result.errorReason}")
                showError(result.errorReason)
            }
        }
    }
    
    // Error handling
    onError = { error ->
        Log.e("GooglePay", "Error: ${error.message}", error)
        showError(error.message)
    }
    
    // User cancellation
    onCancel = {
        Log.d("GooglePay", "Payment cancelled")
    }
}

Payment sheet interactions

Overview

Google Pay's payment sheet supports dynamic interactions that allow you to provide real-time updates based on user selections. This creates a seamless, interactive checkout experience where shipping costs, taxes, and totals update automatically as customers make choices within the payment sheet.

Payment sheet interactions enable you to:

  • Calculate shipping costs dynamically based on the customer's selected address
  • Update transaction amounts in real-time without reloading the payment sheet
  • Validate addresses and show errors for unserviceable locations
  • Offer multiple shipping options with different costs and delivery times
  • Apply discounts or offers based on customer selections
  • Collect email addresses for order confirmations
  • Request billing addresses for payment verification

Payment sheet interactions provide a native app-like experience whilst keeping customers within the Google Pay flow, significantly reducing checkout friction and cart abandonment.

Enabling payment sheet interactions

To enable dynamic interactions, configure your payment request with the required parameters:

import com.pxp.checkout.components.googlepay.types.*

val config = GooglePayButtonComponentConfig().apply {
    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",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = "50.00",
            displayItems = listOf(
                DisplayItem(
                    label = "Subtotal",
                    type = DisplayItemType.LINE_ITEM,
                    price = "45.00"
                ),
                DisplayItem(
                    label = "Estimated Tax",
                    type = DisplayItemType.TAX,
                    price = "5.00"
                )
            )
        ),
        
        // Enable shipping address collection
        shippingAddressRequired = true,
        shippingAddressParameters = ShippingAddressParameters(
            allowedCountryCodes = listOf("GB", "FR", "DE", "US"),
            phoneNumberRequired = true
        )
    )
    
    // Handle payment data changes
    onPaymentDataChanged = { intermediatePaymentData ->
        handlePaymentDataChange(intermediatePaymentData)
    }
}

Shipping address interactions

Collecting shipping addresses

Request shipping addresses by configuring the payment request:

val config = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(/* ... */),
        transactionInfo = TransactionInfo(/* ... */),
        
        // Enable shipping address collection
        shippingAddressRequired = true,
        
        // Configure shipping address parameters
        shippingAddressParameters = ShippingAddressParameters(
            allowedCountryCodes = listOf("GB", "FR", "DE", "US"), // Restrict to specific countries
            phoneNumberRequired = true // Request phone number
        )
    )
    
    onPaymentDataChanged = { intermediatePaymentData ->
        intermediatePaymentData.shippingAddress?.let { address ->
            // Calculate shipping and update totals
            handleShippingAddressChange(address)
        } ?: PaymentDataChangedResponse()
    }
}

Handling address changes

Respond to address changes with updated pricing and shipping options:

suspend fun handleShippingAddressChange(address: Address): PaymentDataChangedResponse {
    Log.d("GooglePay", "Shipping address changed: ${address.countryCode}, ${address.postalCode}")
    
    return try {
        // 1. Validate the address
        if (!validateShippingAddress(address)) {
            return PaymentDataChangedResponse(
                error = PaymentDataError(
                    reason = PaymentDataErrorReason.SHIPPING_ADDRESS_INVALID,
                    message = "Please enter a complete address",
                    intent = PaymentDataErrorIntent.SHIPPING_ADDRESS
                )
            )
        }
        
        // 2. Check if we ship to this location
        if (!checkShippingAvailability(address.countryCode)) {
            return PaymentDataChangedResponse(
                error = PaymentDataError(
                    reason = PaymentDataErrorReason.SHIPPING_ADDRESS_UNSERVICEABLE,
                    message = "We do not currently ship to ${address.countryCode}",
                    intent = PaymentDataErrorIntent.SHIPPING_ADDRESS
                )
            )
        }
        
        // 3. Calculate shipping cost
        val shippingCost = calculateShipping(address)
        
        // 4. Calculate tax
        val subtotal = 45.00
        val tax = calculateTax(address, subtotal)
        
        // 5. Calculate new total
        val newTotal = subtotal + tax + shippingCost
        
        // 6. Update SDK amount (synchronise with backend)
        pxpCheckout.updateAmount(newTotal)
        
        // 7. Return updated transaction info
        PaymentDataChangedResponse(
            newTransactionInfo = TransactionInfo(
                totalPriceStatus = TotalPriceStatus.FINAL,
                totalPrice = newTotal.toPrice(),
                totalPriceLabel = "Total",
                displayItems = listOf(
                    DisplayItem(
                        label = "Subtotal",
                        type = DisplayItemType.LINE_ITEM,
                        price = subtotal.toPrice(),
                        status = DisplayItemStatus.FINAL
                    ),
                    DisplayItem(
                        label = "Shipping",
                        type = DisplayItemType.LINE_ITEM,
                        price = shippingCost.toPrice(),
                        status = DisplayItemStatus.FINAL
                    ),
                    DisplayItem(
                        label = "Tax",
                        type = DisplayItemType.TAX,
                        price = tax.toPrice(),
                        status = DisplayItemStatus.FINAL
                    )
                )
            )
        )
    } catch (e: Exception) {
        Log.e("GooglePay", "Error handling address change", e)
        PaymentDataChangedResponse(
            error = PaymentDataError(
                reason = PaymentDataErrorReason.SHIPPING_ADDRESS_INVALID,
                message = "Unable to calculate shipping. Please try again.",
                intent = PaymentDataErrorIntent.SHIPPING_ADDRESS
            )
        )
    }
}

Address validation examples

fun validateShippingAddress(address: Address): Boolean {
    val requiredFields = listOf(
        address.countryCode,
        address.postalCode,
        address.administrativeArea
    )
    
    if (requiredFields.any { it.isNullOrEmpty() }) {
        Log.e("GooglePay", "Missing required address fields")
        return false
    }
    
    // Validate postal code format for UK
    if (address.countryCode == "GB") {
        val ukPostcodeRegex = Regex("^[A-Z]{1,2}\\d{1,2}[A-Z]?\\s?\\d[A-Z]{2}$", RegexOption.IGNORE_CASE)
        if (!ukPostcodeRegex.matches(address.postalCode ?: "")) {
            Log.e("GooglePay", "Invalid UK postcode format")
            return false
        }
    }
    
    return true
}

suspend fun checkShippingAvailability(countryCode: String): Boolean {
    val shippableCountries = listOf("GB", "US", "CA", "FR", "DE", "ES", "IT")
    return shippableCountries.contains(countryCode)
}

suspend fun calculateShipping(address: Address): Double {
    val shippingRates = mapOf(
        "GB" to 4.99,
        "US" to 9.99,
        "CA" to 12.99,
        "FR" to 8.99,
        "DE" to 8.99,
        "ES" to 8.99,
        "IT" to 8.99
    )
    
    return shippingRates[address.countryCode] ?: 15.99 // Default international rate
}

suspend fun calculateTax(address: Address, subtotal: Double): Double {
    val taxRates = mapOf(
        "GB" to 0.20, // 20% VAT
        "US" to 0.08, // Example sales tax
        "CA" to 0.13, // Example HST
        "FR" to 0.20, // 20% TVA
        "DE" to 0.19  // 19% MwSt
    )
    
    val rate = taxRates[address.countryCode] ?: 0.0
    return subtotal * rate
}

// Helper extension function
fun Double.toPrice(): String = "%.2f".format(this)

Shipping option interactions

Configuring shipping options

Provide multiple shipping methods for customers to choose from:

val config = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(/* ... */),
        transactionInfo = TransactionInfo(/* ... */),
        
        // Enable shipping option selection
        shippingOptionRequired = true,
        
        // Configure available shipping options
        shippingOptionParameters = ShippingOptionParameters(
            defaultSelectedOptionId = "standard",
            shippingOptions = listOf(
                ShippingOption(
                    id = "standard",
                    label = "Standard Delivery",
                    description = "5-7 business days"
                ),
                ShippingOption(
                    id = "express",
                    label = "Express Delivery",
                    description = "2-3 business days"
                ),
                ShippingOption(
                    id = "next-day",
                    label = "Next Day Delivery",
                    description = "Order by 6pm for next day"
                )
            )
        )
    )
    
    onPaymentDataChanged = { intermediatePaymentData ->
        intermediatePaymentData.shippingOptionData?.let { shippingOption ->
            handleShippingOptionChange(shippingOption)
        } ?: PaymentDataChangedResponse()
    }
}

Handling shipping option changes

Update pricing when the customer selects a different shipping method:

fun handleShippingOptionChange(shippingOption: ShippingOptionData): PaymentDataChangedResponse {
    Log.d("GooglePay", "Shipping option changed: ${shippingOption.id}")
    
    // Define shipping costs
    val shippingCosts = mapOf(
        "standard" to 4.99,
        "express" to 9.99,
        "next-day" to 14.99
    )
    
    val subtotal = 45.00
    val tax = 9.00
    val shippingCost = shippingCosts[shippingOption.id] ?: 4.99
    
    // Calculate new total
    val newTotal = subtotal + tax + shippingCost
    
    // Update SDK amount
    pxpCheckout.updateAmount(newTotal)
    
    // Track shipping method selection
    trackEvent("shipping-option-selected", mapOf(
        "optionId" to shippingOption.id,
        "cost" to shippingCost
    ))
    
    return PaymentDataChangedResponse(
        newTransactionInfo = TransactionInfo(
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = newTotal.toPrice(),
            totalPriceLabel = "Total",
            displayItems = listOf(
                DisplayItem(
                    label = "Subtotal",
                    type = DisplayItemType.LINE_ITEM,
                    price = subtotal.toPrice()
                ),
                DisplayItem(
                    label = shippingOption.label,
                    type = DisplayItemType.LINE_ITEM,
                    price = shippingCost.toPrice()
                ),
                DisplayItem(
                    label = "Tax",
                    type = DisplayItemType.TAX,
                    price = tax.toPrice()
                )
            )
        )
    )
}

Email collection

Request the customer's email address for order confirmations:

val config = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(/* ... */),
        transactionInfo = TransactionInfo(/* ... */),
        
        // Request email address
        emailRequired = true
    )
    
    onPostAuthorisation = { result, paymentData ->
        when (result) {
            is MerchantSubmitResult -> {
                // Access email from payment data
                val customerEmail = paymentData.email
                
                // Send confirmation email
                sendOrderConfirmation(
                    email = customerEmail,
                    orderId = result.merchantTransactionId,
                    systemTransactionId = result.systemTransactionId
                )
            }
            else -> { /* Handle failure */ }
        }
    }
}

Billing address collection

Request billing address for verification:

val config = GooglePayButtonComponentConfig().apply {
    paymentDataRequest = PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD
                    ),
                    allowedAuthMethods = listOf(
                        CardAuthMethod.PAN_ONLY,
                        CardAuthMethod.CRYPTOGRAM_3DS
                    ),
                    
                    // Request billing address
                    billingAddressRequired = true,
                    billingAddressParameters = BillingAddressParameters(
                        format = BillingAddressFormat.FULL, // or MIN for minimal details
                        phoneNumberRequired = false
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(/* ... */)
    )
}

Billing address formats

FormatFields included
MINName, country code, postal code
FULLFull billing address with street, city, state, postal code, country

Complete interaction example

Here's a comprehensive example with all interaction types:

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.types.ComponentType

@Composable
fun GooglePayCheckoutWithInteractions(pxpCheckout: PxpCheckout) {
    val googlePayComponent = remember {
        pxpCheckout.createComponent<
            GooglePayButtonComponent,
            GooglePayButtonComponentConfig
        >(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                // Payment configuration with all interactions
                paymentDataRequest = PaymentDataRequest(
                    allowedPaymentMethods = listOf(
                        PaymentMethodSpecification(
                            parameters = PaymentMethodParameters(
                                allowedCardNetworks = listOf(
                                    CardNetwork.VISA,
                                    CardNetwork.MASTERCARD,
                                    CardNetwork.AMEX
                                ),
                                allowedAuthMethods = listOf(
                                    CardAuthMethod.PAN_ONLY,
                                    CardAuthMethod.CRYPTOGRAM_3DS
                                ),
                                billingAddressRequired = true,
                                billingAddressParameters = BillingAddressParameters(
                                    format = BillingAddressFormat.FULL,
                                    phoneNumberRequired = true
                                )
                            )
                        )
                    ),
                    transactionInfo = TransactionInfo(
                        currencyCode = "GBP",
                        countryCode = "GB",
                        totalPriceStatus = TotalPriceStatus.FINAL,
                        totalPrice = "45.00",
                        totalPriceLabel = "Total",
                        displayItems = listOf(
                            DisplayItem(
                                label = "Wireless Headphones",
                                type = DisplayItemType.LINE_ITEM,
                                price = "39.99",
                                status = DisplayItemStatus.FINAL
                            ),
                            DisplayItem(
                                label = "Estimated Tax",
                                type = DisplayItemType.TAX,
                                price = "5.01",
                                status = DisplayItemStatus.PENDING
                            )
                        )
                    ),
                    
                    // Email collection
                    emailRequired = true,
                    
                    // Shipping address collection
                    shippingAddressRequired = true,
                    shippingAddressParameters = ShippingAddressParameters(
                        allowedCountryCodes = listOf("GB", "FR", "DE", "ES", "IT", "NL", "BE"),
                        phoneNumberRequired = true
                    ),
                    
                    // Shipping options
                    shippingOptionRequired = true,
                    shippingOptionParameters = ShippingOptionParameters(
                        defaultSelectedOptionId = "standard",
                        shippingOptions = listOf(
                            ShippingOption(
                                id = "standard",
                                label = "Standard Delivery",
                                description = "5-7 business days"
                            ),
                            ShippingOption(
                                id = "express",
                                label = "Express Delivery",
                                description = "2-3 business days"
                            )
                        )
                    )
                )
                
                // Handle dynamic updates
                onPaymentDataChanged = { intermediatePaymentData ->
                    handleDynamicPaymentUpdates(intermediatePaymentData, pxpCheckout)
                }
                
                // Handle successful payment
                onPostAuthorisation = { result, paymentData ->
                    when (result) {
                        is MerchantSubmitResult -> {
                            Log.d("GooglePay", "Payment successful with collected data: " +
                                "email=${paymentData.email}, " +
                                "shippingAddress=${paymentData.shippingAddress}, " +
                                "shippingOption=${paymentData.shippingOption}")
                            
                            // Send confirmation email
                            sendOrderConfirmation(
                                email = paymentData.email,
                                orderId = result.merchantTransactionId,
                                systemTransactionId = result.systemTransactionId,
                                shippingAddress = paymentData.shippingAddress
                            )
                            
                            navigateToOrderConfirmation()
                        }
                        else -> { /* Handle failure */ }
                    }
                }
            }
        )
    }
    
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(
            text = "Complete Your Purchase",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        googlePayComponent?.Content(
            modifier = Modifier
                .fillMaxWidth()
                .height(56.dp)
        )
    }
}

fun handleDynamicPaymentUpdates(
    intermediatePaymentData: IntermediatePaymentData,
    pxpCheckout: PxpCheckout
): PaymentDataChangedResponse {
    Log.d("GooglePay", "Payment data changed: " +
        "hasShippingAddress=${intermediatePaymentData.shippingAddress != null}, " +
        "hasShippingOption=${intermediatePaymentData.shippingOptionData != null}")
    
    return try {
        val subtotal = 39.99
        var shippingCost = 0.0
        var tax = 0.0
        var error: PaymentDataError? = null
        
        // Handle shipping address change
        intermediatePaymentData.shippingAddress?.let { address ->
            // Validate address
            if (address.postalCode.isNullOrEmpty() || address.countryCode.isNullOrEmpty()) {
                error = PaymentDataError(
                    reason = PaymentDataErrorReason.SHIPPING_ADDRESS_INVALID,
                    message = "Please enter a complete address",
                    intent = PaymentDataErrorIntent.SHIPPING_ADDRESS
                )
            }
            // Check if we ship to this country
            else if (address.countryCode !in listOf("GB", "FR", "DE", "ES", "IT", "NL", "BE")) {
                error = PaymentDataError(
                    reason = PaymentDataErrorReason.SHIPPING_ADDRESS_UNSERVICEABLE,
                    message = "We do not ship to ${address.countryCode}",
                    intent = PaymentDataErrorIntent.SHIPPING_ADDRESS
                )
            }
            // Calculate shipping and tax
            else {
                val shippingRates = mapOf(
                    "GB" to 4.99,
                    "FR" to 8.99,
                    "DE" to 8.99,
                    "ES" to 8.99,
                    "IT" to 8.99,
                    "NL" to 8.99,
                    "BE" to 8.99
                )
                shippingCost = shippingRates[address.countryCode] ?: 4.99
                
                // Calculate tax
                val taxRates = mapOf(
                    "GB" to 0.20,
                    "FR" to 0.20,
                    "DE" to 0.19,
                    "ES" to 0.21,
                    "IT" to 0.22,
                    "NL" to 0.21,
                    "BE" to 0.21
                )
                tax = subtotal * (taxRates[address.countryCode] ?: 0.20)
            }
        }
        
        // Handle shipping option change
        intermediatePaymentData.shippingOptionData?.let { option ->
            val shippingCosts = mapOf(
                "standard" to 4.99,
                "express" to 9.99
            )
            shippingCost = shippingCosts[option.id] ?: 4.99
        }
        
        // Calculate new total
        val newTotal = subtotal + tax + shippingCost
        
        // Update SDK amount
        pxpCheckout.updateAmount(newTotal)
        
        // Return response
        PaymentDataChangedResponse(
            newTransactionInfo = TransactionInfo(
                totalPriceStatus = TotalPriceStatus.FINAL,
                totalPrice = newTotal.toPrice(),
                totalPriceLabel = "Total",
                displayItems = listOf(
                    DisplayItem(
                        label = "Wireless Headphones",
                        type = DisplayItemType.LINE_ITEM,
                        price = subtotal.toPrice(),
                        status = DisplayItemStatus.FINAL
                    ),
                    DisplayItem(
                        label = "Shipping",
                        type = DisplayItemType.LINE_ITEM,
                        price = shippingCost.toPrice(),
                        status = DisplayItemStatus.FINAL
                    ),
                    DisplayItem(
                        label = "VAT",
                        type = DisplayItemType.TAX,
                        price = tax.toPrice(),
                        status = DisplayItemStatus.FINAL
                    )
                )
            ),
            error = error
        )
    } catch (e: Exception) {
        Log.e("GooglePay", "Error in onPaymentDataChanged", e)
        PaymentDataChangedResponse(
            error = PaymentDataError(
                reason = PaymentDataErrorReason.OTHER_ERROR,
                message = "An error occurred. Please try again.",
                intent = PaymentDataErrorIntent.SHIPPING_ADDRESS
            )
        )
    }
}

CVC collection

Configure when and how to collect CVC codes:

// CVC collection strategy
collectCvc = GooglePayButtonComponentConfig.CvcCollectionStrategy.DEFAULT

// Custom CVC dialog (optional)
cvcVerificationPopupConfig = CvcVerificationDialogConfig(
    title = "Security Verification",
    description = "Please enter your card's CVV code",
    submitButtonText = "Confirm Payment",
    cancelButtonText = "Go Back"
)

These strategies control when the SDK prompts customers to enter their CVV/CVC code:

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

3D Secure authentication

Authentication strategy

The useUnityAuthenticationStrategy property controls how 3DS authentication is handled:

useUnityAuthenticationStrategy: Boolean = false
  • true: Uses Unity's automatic authentication strategy. The SDK automatically handles authentication decisions and session updates based on backend responses. This is the recommended approach as it simplifies integration and ensures consistent authentication handling.
  • false (default): Uses manual authentication callbacks. You must handle authentication decisions and session updates in onPostInitiateAuthentication and onPostAuthentication callbacks.

When using useUnityAuthenticationStrategy = true, you still need to provide the onPreInitiateAuthentication and onPreAuthentication callbacks to configure authentication parameters, but the SDK will automatically handle the post-authentication logic.

Example configuration

Enable 3DS authentication with Unity's automatic strategy:

val config = GooglePayButtonComponentConfig().apply {
    // Enable Unity's automatic 3DS handling
    useUnityAuthenticationStrategy = true
    
    // Minimal 3DS configuration
    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
        )
    }
}

See 3DS for detailed 3DS configuration.

Enable consent collection for payment tokens:

// 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
    )
)

// Attach to Google Pay config
val googlePayConfig = GooglePayButtonComponentConfig().apply {
    googlePayConsentComponent = consentComponent
    
    // Or use callback
    onGetConsent = {
        consentComponent?.getValue() as? Boolean ?: false
    }
}

See Recurring payments for more details.

Component lifecycle

Creating components

val googlePayComponent = pxpCheckout.createComponent<
    GooglePayButtonComponent,
    GooglePayButtonComponentConfig
>(
    type = ComponentType.GOOGLE_PAY_BUTTON,
    config = googlePayConfig
)

Rendering components

@Composable
fun PaymentScreen(pxpCheckout: PxpCheckout) {
    val googlePayComponent = remember {
        pxpCheckout.createComponent<
            GooglePayButtonComponent,
            GooglePayButtonComponentConfig
        >(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = googlePayConfig
        )
    }
    
    // Render the component
    googlePayComponent?.Content(
        modifier = Modifier
            .fillMaxWidth()
            .height(56.dp)
    )
}

Complete example

Here's a complete configuration example:

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.models.*
import com.pxp.checkout.services.kount.*
import com.pxp.checkout.types.ComponentType

@Composable
fun GooglePayCheckout(pxpCheckout: PxpCheckout) {
    val googlePayComponent = remember {
        pxpCheckout.createComponent<
            GooglePayButtonComponent,
            GooglePayButtonComponentConfig
        >(
            type = ComponentType.GOOGLE_PAY_BUTTON,
            config = GooglePayButtonComponentConfig().apply {
                // Payment configuration
                paymentDataRequest = PaymentDataRequest(
                    allowedPaymentMethods = listOf(
                        PaymentMethodSpecification(
                            parameters = PaymentMethodParameters(
                                allowedCardNetworks = listOf(
                                    CardNetwork.VISA,
                                    CardNetwork.MASTERCARD,
                                    CardNetwork.AMEX
                                ),
                                allowedAuthMethods = listOf(
                                    CardAuthMethod.PAN_ONLY,
                                    CardAuthMethod.CRYPTOGRAM_3DS
                                ),
                                billingAddressRequired = true,
                                billingAddressParameters = BillingAddressParameters(
                                    format = BillingAddressFormat.FULL,
                                    phoneNumberRequired = true
                                )
                            )
                        )
                    ),
                    transactionInfo = TransactionInfo(
                        currencyCode = "GBP",
                        totalPriceStatus = TotalPriceStatus.FINAL,
                        totalPrice = "99.99",
                        countryCode = "GB"
                    ),
                    emailRequired = true
                )
                
                // Button styling
                style = GooglePayButtonStyle(
                    type = GooglePayButtonType.PAY,
                    theme = GooglePayButtonTheme.DARK,
                    cornerRadius = 8
                )
                
                // Enable 3DS
                useUnityAuthenticationStrategy = true
                
                // CVC collection
                collectCvc = GooglePayButtonComponentConfig.CvcCollectionStrategy.DEFAULT
                
                // Pre-authorisation
                onPreAuthorisation = suspend { 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"
                                    )
                                )
                            )
                        )
                    )
                }
                
                // Post-authorisation
                onPostAuthorisation = { result, paymentData ->
                    when (result) {
                        is MerchantSubmitResult -> {
                            // Payment successful
                            navigateToSuccess(result.merchantTransactionId)
                        }
                        is FailedSubmitResult -> {
                            // Payment failed
                            showError(result.errorReason)
                        }
                    }
                }
                
                // Error handling
                onError = { error ->
                    showError(error.message)
                }
                
                // Cancellation
                onCancel = {
                    Log.d("GooglePay", "Payment cancelled")
                }
            }
        )
    }
    
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(
            text = "Complete Your Purchase",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        googlePayComponent?.Content(
            modifier = Modifier
                .fillMaxWidth()
                .height(56.dp)
        )
    }
}

Best practices

Configuration organisation

Extract configuration into separate functions for better maintainability:

fun createGooglePayConfig(
    amount: String,
    onSuccess: (String) -> Unit,
    onError: (String) -> Unit
): GooglePayButtonComponentConfig {
    return GooglePayButtonComponentConfig().apply {
        paymentDataRequest = createPaymentDataRequest(amount)
        style = createButtonStyle()
        useUnityAuthenticationStrategy = true
        
        onPreAuthorisation = suspend { data ->
            createTransactionInitData()
        }
        
        onPostAuthorisation = { result, _ ->
            handlePaymentResult(result, onSuccess, onError)
        }
    }
}

private fun createPaymentDataRequest(amount: String): PaymentDataRequest {
    return PaymentDataRequest(
        allowedPaymentMethods = listOf(
            PaymentMethodSpecification(
                parameters = PaymentMethodParameters(
                    allowedCardNetworks = listOf(
                        CardNetwork.VISA,
                        CardNetwork.MASTERCARD
                    ),
                    allowedAuthMethods = listOf(
                        CardAuthMethod.PAN_ONLY,
                        CardAuthMethod.CRYPTOGRAM_3DS
                    )
                )
            )
        ),
        transactionInfo = TransactionInfo(
            currencyCode = "GBP",
            totalPriceStatus = TotalPriceStatus.FINAL,
            totalPrice = amount
        )
    )
}

Error handling

Always implement comprehensive error handling:

onError = { error ->
    when (error) {
        is GooglePayNotReadyException -> {
            showError("Google Pay is not available on this device")
            showAlternativePaymentMethods()
        }
        is GooglePayConfigurationValidationFailedException -> {
            Log.e("GooglePay", "Configuration error", error)
            showError("Payment system configuration error")
        }
        else -> {
            Log.e("GooglePay", "Unexpected error", error)
            showError("An error occurred. Please try again.")
        }
    }
}