Enable fast, secure checkout with Google Pay for Android customers.
Google Pay is automatically included in the drop-in when enabled in your session and when the customer's device supports it. The drop-in handles all Google Pay setup, button rendering, and payment processing automatically.
- Automatic detection: Only shows when the device supports Google Pay
- Zero configuration: Drop-in handles all Google Pay setup
- Native experience: Uses the device's native Google Pay sheet
- Unified callbacks: Same
onSuccess/onErroras other payment methods - Fast checkout: Customers authenticate and complete payment in seconds
When a customer selects Google Pay:
- The customer taps "Google Pay" in the accordion. The native payment sheet appears.
- The customer authenticates with their device (fingerprint, PIN).
- The customer approves the payment. The payment sheet closes automatically.
- The payment is processed through Unity.
- Your
onSuccesscallback fires.
Configure Google Pay-specific settings including merchant information, button customization, address collection, and card restrictions.
The following properties are available for Google Pay configuration:
| Property | Description |
|---|---|
merchantNameString | Merchant name shown in Google Pay sheet. Should match your business name. |
merchantIdString | Google Pay merchant ID (obtained from Google). Required for production. |
buttonTypeGooglePayButtonType | Button style: Plain, Buy, Checkout, Donate, Order, Pay, Subscribe. Defaults to Pay. |
buttonThemeGooglePayButtonTheme | Button theme: Dark or Light. Defaults to Dark. |
allowedCardNetworksList<String> | Which card brands to accept (e.g., listOf("VISA", "MASTERCARD")). Falls back to session configuration. |
allowedCardAuthMethodsList<String> | Card authentication methods: PAN_ONLY or CRYPTOGRAM_3DS. Defaults to both. |
allowPrepaidCardsBoolean | Allow prepaid cards. Defaults to true. |
allowCreditCardsBoolean | Allow credit cards. Defaults to true. |
billingAddressRequiredBoolean | Request billing address from customer. Defaults to false. |
billingAddressFormatBillingAddressFormat | Billing address format: Min (postal code and country) or Full (complete address). Defaults to Min. |
emailRequiredBoolean | Request email address from customer. Defaults to false. |
shippingAddressRequiredBoolean | Request shipping address from customer. Defaults to false. |
shippingAddressAllowedCountryCodesList<String> | Restrict shipping to specific countries (e.g., listOf("GB", "US", "FR")). |
Google Pay inherits the following settings from methodConfig.global:
| Property | Description |
|---|---|
onCancel(paymentMethod: PaymentMethod, data: Any?) -> Unit | Called when user cancels Google Pay payment (dismisses payment sheet). |
This example shows a full Google Pay configuration with global settings and Google Pay-specific options:
methodConfig = DropInMethodConfig(
// Global callbacks that apply to Google Pay
global = DropInGlobalConfig(
// Handle Google Pay cancellation
onCancel = { paymentMethod, data ->
if (paymentMethod == PaymentMethod.GOOGLE_PAY) {
Log.d("Checkout", "Google Pay payment cancelled: $data")
// Track analytics
analytics.trackEvent("google_pay_cancelled", mapOf(
"timestamp" to System.currentTimeMillis()
))
// Update UI
Toast.makeText(
context,
"Google Pay payment was cancelled. Please try again.",
Toast.LENGTH_SHORT
).show()
}
}
),
googlePay = DropInGooglePayConfig(
// Merchant information
merchantName = "Demo Store",
merchantId = "BCR2DN4TZXY2MLBU", // Your Google Pay merchant ID
// Button customisation
buttonType = GooglePayButtonType.Buy,
buttonTheme = GooglePayButtonTheme.Dark,
// Allowed payment methods
allowedCardNetworks = listOf("VISA", "MASTERCARD", "AMEX"),
allowedCardAuthMethods = listOf("PAN_ONLY", "CRYPTOGRAM_3DS"),
// Card restrictions
allowPrepaidCards = true,
allowCreditCards = true,
// Billing address collection
billingAddressRequired = true,
billingAddressFormat = BillingAddressFormat.Full,
// Contact information
emailRequired = true,
// Shipping address collection
shippingAddressRequired = true,
shippingAddressAllowedCountryCodes = listOf("GB", "US", "FR", "DE")
)
)Google Pay requires the following to function correctly:
- Device compatibility: Google Pay works on Android 7.0 (API level 24) or higher
- Google Play Services: Google Play Services must be installed and up to date
- Customer setup: The customer must have at least one card added to their Google Pay account
- HTTPS: Your backend endpoints must be served over HTTPS
- Unity Portal configuration: Google Pay must be enabled and configured in the Unity Portal
For testing, you can omit the merchantId property. However, you must provide a valid Google Pay merchant ID for production deployments.
Google Pay works through the standard implementation, with no Google Pay-specific code needed:
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.pxp.checkout.checkoutdropin.CheckoutDropIn
import com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig
import com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent
import com.pxp.checkout.models.DropInTransactionData
import com.pxp.checkout.models.DropInTransactionIntentData
import com.pxp.checkout.models.Environment
import com.pxp.checkout.models.EntryType
import com.pxp.checkout.models.IntentType
import com.pxp.checkout.checkoutdropin.types.DropInSubmitResult
import com.pxp.checkout.exceptions.BaseSdkException
@Composable
fun CheckoutScreen() {
val context = LocalContext.current
var sessionData by remember { mutableStateOf<SessionData?>(null) }
var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }
var isLoading by remember { mutableStateOf(true) }
// Fetch session from backend (with Google Pay enabled)
LaunchedEffect(Unit) {
try {
val response = apiClient.post("/api/create-session") {
contentType(ContentType.Application.Json)
}
if (response.status.value == 200) {
val result = response.body<SessionResponse>()
if (result.success && result.data != null) {
sessionData = result.data
} else {
Log.e("Checkout", "Failed to create session: ${result.error}")
}
}
} catch (e: Exception) {
Log.e("Checkout", "Error creating session", e)
} finally {
isLoading = false
}
}
// Initialize Drop-in once session is loaded
LaunchedEffect(sessionData) {
sessionData?.let { session ->
val checkoutDropIn = CheckoutDropIn.initialize(
context = context,
config = CheckoutDropInConfig(
environment = Environment.TEST,
session = session,
ownerType = "MerchantGroup",
ownerId = "MERCHANT-1",
transactionData = DropInTransactionData(
currency = "GBP",
amount = 99.99,
entryType = EntryType.Ecom,
intent = DropInTransactionIntentData(
card = IntentType.Purchase // Google Pay uses card intent
),
merchant = "Demo Store",
merchantTransactionId = { UUID.randomUUID().toString() },
merchantTransactionDate = { Instant.now().toString() }
),
onGetShopper = {
Shopper(id = "shopper-123")
},
onSuccess = { result: DropInSubmitResult ->
Log.d("Checkout", "Google Pay payment successful!")
Log.d("Checkout", "System transaction ID: ${result.systemTransactionId}")
Log.d("Checkout", "Payment method: ${result.paymentMethod}") // Will be "GooglePay"
// CRITICAL: Verify on backend
coroutineScope.launch {
verifyPaymentOnBackend(result)
}
},
onError = { error: BaseSdkException ->
Log.e("Checkout", "Google Pay payment failed", error)
Toast.makeText(
context,
"Payment failed: ${error.message}",
Toast.LENGTH_LONG
).show()
}
)
)
checkoutDropInComponent = checkoutDropIn.create()
}
}
// Render Drop-in
if (isLoading) {
CircularProgressIndicator()
} else {
checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())
}
}Enable Google Pay in your session request:
// BACKEND: Create a session with Google Pay enabled
const sessionRequest = {
merchant: "MERCHANT-1",
site: "SITE-1",
sessionTimeout: 120,
merchantTransactionId: crypto.randomUUID(),
transactionMethod: {
intent: {
card: "Purchase" // Google Pay uses card intent
}
},
amounts: {
currencyCode: "GBP",
transactionValue: 99.99
},
allowedFundingTypes: {
wallets: {
googlePay: {
// Google Pay configuration from the Unity Portal will be used
// You can optionally specify merchant information here
merchantName: "Demo Store",
merchantId: "BCR2DN4TZXY2MLBU"
}
}
},
allowTransaction: true,
serviceType: "CheckoutDropIn"
};Drop-in supports two Google Pay payment flows, configured via the intent parameter:
Immediate, single-step payment where funds are captured right away.
import com.pxp.checkout.models.DropInTransactionData
import com.pxp.checkout.models.DropInTransactionIntentData
import com.pxp.checkout.models.EntryType
import com.pxp.checkout.models.IntentType
transactionData = DropInTransactionData(
currency = "GBP",
amount = 99.99,
entryType = EntryType.Ecom,
intent = DropInTransactionIntentData(
card = IntentType.Purchase // Pay Now flow - Google Pay uses card intent
),
merchant = "Demo Store",
merchantTransactionId = { UUID.randomUUID().toString() },
merchantTransactionDate = { Instant.now().toString() }
)Use this flow for:
- Digital products
- Simple orders with immediate fulfillment
- Subscriptions
- Donations
When a Google Pay payment succeeds, your onSuccess callback receives the same standard result as other payment methods:
onSuccess = { result: DropInSubmitResult ->
Log.d("Checkout", "Payment Details:")
Log.d("Checkout", "- System Transaction ID: ${result.systemTransactionId}")
Log.d("Checkout", "- Merchant Transaction ID: ${result.merchantTransactionId}")
Log.d("Checkout", "- Payment Method: ${result.paymentMethod}") // "GooglePay"
// Note: Amount, currency, and card details must be retrieved from backend
// Google Pay tokenises cards - actual card details are not exposed
}Handle Google Pay-specific errors:
import com.pxp.checkout.exceptions.BaseSdkException
onError = { error: BaseSdkException ->
Log.e("Checkout", "Error code: ${error.code}")
Log.e("Checkout", "Error message: ${error.message}")
// Handle user cancellation
if (error.message?.contains("cancelled", ignoreCase = true) == true ||
error.message?.contains("closed", ignoreCase = true) == true) {
Log.d("Checkout", "User cancelled Google Pay payment")
// Don't show error - user intentionally cancelled
return@CheckoutDropInConfig
}
// Handle Google Pay-specific errors based on message content
when {
error.message?.contains("not available", ignoreCase = true) == true -> {
Toast.makeText(
context,
"Google Pay isn't available on this device. Please use a different payment method.",
Toast.LENGTH_LONG
).show()
}
error.message?.contains("not supported", ignoreCase = true) == true -> {
Toast.makeText(
context,
"Google Pay isn't supported for this transaction. Please use a different payment method.",
Toast.LENGTH_LONG
).show()
}
error.message?.contains("declined", ignoreCase = true) == true -> {
Toast.makeText(
context,
"Payment declined. Please try a different payment method.",
Toast.LENGTH_LONG
).show()
}
error.message?.contains("timeout", ignoreCase = true) == true -> {
Toast.makeText(
context,
"Google Pay timed out. Please try again.",
Toast.LENGTH_SHORT
).show()
}
else -> {
Toast.makeText(
context,
"Google Pay payment failed: ${error.message}",
Toast.LENGTH_LONG
).show()
}
}
}The following table describes common Google Pay error scenarios:
| Scenario | How to detect | Recommended action |
|---|---|---|
| User cancelled | error.message contains "cancelled" or "closed" | No alert needed - user action was intentional |
| Not available | error.message contains "not available" or "not supported" | Google Pay button should be hidden automatically |
| Payment declined | error.message contains "declined" | Suggest trying different payment method |
| Configuration error | error.message contains "configuration" or "setup" | Contact support, check Unity Portal settings |
| Network error | Network-related error message | Check connection and retry |
Google Pay errors return descriptive messages rather than specific error code constants. Check the error.message property for error details. You can also use the onCancel callback in methodConfig.global to specifically handle user cancellations.
Always verify Google Pay payments on your backend to ensure payment success before fulfilling orders:
import com.pxp.checkout.checkoutdropin.types.DropInSubmitResult
onSuccess = { result: DropInSubmitResult ->
// Send to backend for verification
coroutineScope.launch {
try {
val response = apiClient.post("/api/verify-payment") {
contentType(ContentType.Application.Json)
setBody(mapOf(
"systemTransactionId" to result.systemTransactionId,
"merchantTransactionId" to result.merchantTransactionId
))
}
if (response.status.value == 200) {
val verified = response.body<VerificationResponse>()
if (verified.success) {
// Navigate to success screen
navController.navigate("success?orderId=${verified.orderId}")
} else {
Toast.makeText(
context,
"Payment verification failed",
Toast.LENGTH_LONG
).show()
}
}
} catch (e: Exception) {
Log.e("Checkout", "Verification error", e)
Toast.makeText(
context,
"Failed to verify payment",
Toast.LENGTH_LONG
).show()
}
}
}Use the following backend code to verify Google Pay transactions via the PXP API:
// BACKEND: Verify Google Pay payment
app.post('/api/verify-payment', async (req, res) => {
const { systemTransactionId, merchantTransactionId } = req.body;
try {
// Query PXP API to get transaction details
const txnPath = `api/v1/transactions/${systemTransactionId}`;
const { authHeader, requestId } = createAuthHeader(
txnPath,
'',
process.env.PXP_TOKEN_ID,
process.env.PXP_TOKEN_VALUE
);
const transaction = await fetch(
`https://api-services.pxp.io/${txnPath}`,
{
headers: {
'X-Client-Id': process.env.PXP_CLIENT_ID,
'X-Request-Id': requestId,
'Authorization': authHeader
}
}
).then(r => r.json());
// Verify transaction state
if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {
return res.json({ success: false, error: 'Transaction not successful' });
}
// Verify merchant transaction ID matches
if (transaction.merchantTransactionId !== merchantTransactionId) {
return res.json({ success: false, error: 'Transaction ID mismatch' });
}
// Verify amount matches expected amount from your order records
const order = await getOrderByMerchantTransactionId(merchantTransactionId);
const txnAmount = transaction.amounts?.transactionValue || transaction.amount || 0;
if (Math.abs(txnAmount - order.amount) > 0.01) {
return res.json({ success: false, error: 'Amount mismatch' });
}
// Google Pay payments show as Card funding type in PXP API
const fundingType = transaction.fundingData?.fundingType ||
transaction.fundingType ||
'Unknown';
if (fundingType !== 'Card') {
return res.json({ success: false, error: 'Invalid funding type' });
}
// Fulfill order
const orderId = await fulfillOrder(transaction);
return res.json({ success: true, orderId });
} catch (error) {
console.error('Verification error:', error);
return res.json({ success: false, error: 'Verification failed' });
}
});Use the following test cards in the Google Pay test environment:
| Card network | Test card number | Expiry | CVC |
|---|---|---|---|
| Visa | 4111 1111 1111 1111 | Any future date | Any 3 digits |
| Mastercard | 5555 5555 5555 4444 | Any future date | Any 3 digits |
| Amex | 3782 822463 10005 | Any future date | Any 4 digits |
To test Google Pay, you must add these test cards to your Google account in the test environment. Open Google Wallet on your test device and add the test cards manually.
Configure your app for Google Pay testing:
// Use test environment for Google Pay testing
CheckoutDropInConfig(
environment = Environment.TEST, // Test environment
// ... other config
methodConfig = DropInMethodConfig(
googlePay = DropInGooglePayConfig(
// Omit merchantId for testing
merchantName = "Demo Store",
// ... other config
)
)
)