Learn how to configure the Google Pay button component for Android with Kotlin and Jetpack Compose.
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 |
|---|---|
paymentDataRequestPaymentDataRequest required | The Google Pay payment request configuration containing payment methods and transaction details. |
paymentDataRequest.allowedPaymentMethodsList<PaymentMethodSpecification> required | List of supported payment methods. At least one payment method is required. |
paymentDataRequest.allowedPaymentMethods[].parametersPaymentMethodParameters required | Payment method parameters including card networks and authentication methods. |
paymentDataRequest.allowedPaymentMethods[].parameters.allowedCardNetworksList<CardNetwork> required | Supported card networks. Possible values:
|
paymentDataRequest.allowedPaymentMethods[].parameters.allowedAuthMethodsList<CardAuthMethod> required | Supported authentication methods. Possible values:
|
paymentDataRequest.transactionInfoTransactionInfo required | Details about the transaction, including the amount and currency. |
paymentDataRequest.transactionInfo.currencyCodeString required | The currency code, in ISO 4217 format (e.g., USD, GBP, EUR). |
paymentDataRequest.transactionInfo.totalPriceStatusTotalPriceStatus required | The price finality status. Possible values:
|
paymentDataRequest.transactionInfo.totalPriceString required | The total monetary value of the transaction as a string (e.g., 99.99). |
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}")
}
}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
)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
}enum class GooglePayButtonTheme {
DARK, // Black background with white text
LIGHT // White background with black text
}Control button size using standard Compose modifiers:
googlePayComponent?.Content(
modifier = Modifier
.fillMaxWidth() // Full width
.height(48.dp) // Fixed height
)Configure callbacks to handle payment events. See Events for detailed documentation.
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")
}
}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.
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)
}
}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()
}
}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
)
)
}
}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)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()
}
}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()
)
)
)
)
}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 */ }
}
}
}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(/* ... */)
)
}| Format | Fields included |
|---|---|
MIN | Name, country code, postal code |
FULL | Full billing address with street, city, state, postal code, country |
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
)
)
}
}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
}The useUnityAuthenticationStrategy property controls how 3DS authentication is handled:
useUnityAuthenticationStrategy: Boolean = falsetrue: 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 inonPostInitiateAuthenticationandonPostAuthenticationcallbacks.
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.
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.
val googlePayComponent = pxpCheckout.createComponent<
GooglePayButtonComponent,
GooglePayButtonComponentConfig
>(
type = ComponentType.GOOGLE_PAY_BUTTON,
config = googlePayConfig
)@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)
)
}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)
)
}
}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
)
)
}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.")
}
}
}