Learn about how to configure the Google Pay button component for Web.
At minimum, the Google Pay button component requires the following configuration to function:
The SDK automatically configures tokenizationSpecification with the correct gateway and merchant ID from your session. You only need to provide allowedPaymentMethods with the card parameters.
const config = {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
}
}],
transactionInfo: {
currencyCode: 'USD',
totalPriceStatus: 'FINAL',
totalPrice: '99.99'
}
}
};| Property | Description |
|---|---|
paymentDataRequestGooglePayPaymentDataRequest required | The Google Pay payment request configuration containing payment methods and transaction details. |
paymentDataRequest.allowedPaymentMethodsarray required | An array specifying the supported payment methods. At least one payment method is required. |
paymentDataRequest.allowedPaymentMethods[].typestring required | The payment method type. This must be set to CARD. |
paymentDataRequest.allowedPaymentMethods[].parametersobject required | Configuration for the payment method. |
paymentDataRequest.allowedPaymentMethods[].parameters.allowedCardNetworksarray of strings required | The supported card networks. Possible values:
|
paymentDataRequest.allowedPaymentMethods[].parameters.allowedAuthMethodsarray of strings required | The supported authentication methods. Possible values:
|
paymentDataRequest.transactionInfoobject 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.totalPriceStatusstring 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:
const config = {
// Payment details
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
phoneNumberRequired: true
}
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'merchant-12345'
}
}
}],
transactionInfo: {
currencyCode: 'USD',
totalPriceStatus: 'FINAL',
totalPrice: '99.99',
totalPriceLabel: 'Total',
displayItems: [{
label: 'Subtotal',
type: 'SUBTOTAL',
price: '89.99'
}, {
label: 'Tax',
type: 'TAX',
price: '10.00'
}]
},
merchantInfo: {
merchantName: 'Your store',
merchantId: 'BCR2DN4TZ6...'
},
emailRequired: true,
shippingAddressRequired: true,
shippingAddressParameters: {
allowedCountryCodes: ['US', 'GB'],
phoneNumberRequired: true
},
shippingOptionRequired: true,
shippingOptionParameters: {
defaultSelectedOptionId: 'standard',
shippingOptions: [{
id: 'standard',
label: 'Standard shipping (5-7 days)',
description: 'Standard delivery',
price: '5.99'
}, {
id: 'express',
label: 'Express shipping (2-3 days)',
description: 'Faster delivery',
price: '12.99'
}]
}
},
// Button appearance
style: {
type: 'buy',
color: 'default',
height: '48px',
width: '100%',
borderRadius: 8,
borderType: 'default_border',
sizeMode: 'fill',
locale: 'en-US'
},
// Payment options
existingPaymentMethodRequired: false,
collectCvc: 'default',
// Consent component
googlePayConsentComponent: consentInstance,
onGetConsent: () => {
return document.getElementById('consent-checkbox')?.checked || false;
},
// Event handlers
onPreAuthorisation: async (data) => {
return {
riskScreeningData: {
performRiskScreening: true
}
};
},
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
window.location.href = '/success';
}
},
onPaymentDataChanged: async (intermediatePaymentData) => {
return {
newTransactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: calculateTotal(intermediatePaymentData).toFixed(2)
}
};
},
onError: (error) => {
console.error('Google Pay error:', error);
showError(error.message);
},
onCancel: () => {
console.log('Payment cancelled by user');
}
};| Property | Description |
|---|---|
paymentDataRequestGooglePayPaymentDataRequest | The Google Pay payment request configuration including payment methods, transaction info, merchant info, and shipping options. See Google Pay API reference. |
styleGooglePayButtonStyle | Button styling configuration including type, colour, dimensions, and locale. Defaults to standard styling. |
existingPaymentMethodRequiredboolean | Whether to require the user to have an existing payment method saved in their Google account. Defaults to false. |
collectCvcstring | The CVC collection mode. Defaults to default.Possible values:
|
cvcVerificationPopupConfigobject | Configuration for CVC verification popup display. Defaults to {}. |
googlePayConsentComponentGooglePayConsentComponent | Consent component instance for payment token storage. Defaults to null. See Recurring payments. |
onGetConsent() => boolean | Callback to check if user has given consent for token storage. Return true if consented, false otherwise. |
onPreAuthorisation(data) => Promise<GooglePayTransactionInitData | null> | Event handler called before payment authorisation to provide additional transaction data. |
onPostAuthorisation(result, paymentData) => void | Event handler called after payment authorisation completes. |
onPaymentDataChangedgoogle.payments.api.PaymentDataChangedHandler | Event handler for dynamic payment sheet updates. See Payment sheet interactions below for detailed implementation guide. |
onGooglePaymentButtonClicked(event) => Promise<void> | Event handler called when the Google Pay button is clicked. |
onCustomValidation() => Promise<boolean> | Custom validation handler called before opening the payment sheet. Return true to proceed, false to block. |
onPreRetrySoftDecline(result) => boolean | object | Handler for retry logic after soft decline responses. See 3D Secure. |
onError(error) => void | Event handler called when an error occurs during the payment flow. |
onCancel() => void | Event handler called when the user cancels the payment. |
You can find Google's official rules around button styling in their Google Pay Brand Guidelines and API reference.
The Google Pay button component renders with these default styles:
style = {
type: 'buy',
color: 'default',
height: '48px',
width: '100%',
borderRadius: 4,
borderType: 'default_border',
sizeMode: 'fill'
}You can override the default appearance by providing custom styles:
const config = {
paymentDataRequest: {
// ... payment configuration
},
style: {
type: 'checkout',
color: 'black',
height: '56px',
width: '100%',
borderRadius: 12,
borderType: 'no_border',
sizeMode: 'fill',
locale: 'en-GB'
}
};| Property | Description |
|---|---|
styleGooglePayButtonStyle | Styling options for the Google Pay button appearance. |
style.typestring | The button type. Defaults to buy.Possible values:
|
style.colorstring | The colour of the Google Pay button. Defaults to default.Possible values:
|
style.heightstring | The button height (e.g., 48px, 56px). Minimum 40px, maximum 72px. Defaults to 48px. |
style.widthstring | The button width (e.g., 100%, 240px). Defaults to 100%. |
style.borderRadiusnumber | The border radius in pixels (0-24). Defaults to 4. |
style.borderTypestring | The border style. Defaults to default_border.Possible values:
|
style.sizeModestring | How the button should size itself. Defaults to fill.Possible values:
|
style.localestring | The language/region code (e.g., en-US, fr-FR, ja-JP). Auto-detected from browser if not specified. |
style.buttonRootNodeHTMLDocument | ShadowRoot | The DOM context for the button (for Shadow DOM use cases). Defaults to document. |
Google Pay has minimum and maximum size requirements to ensure button legibility and brand compliance. The minimum button height is 40px, and the maximum is 72px.
| Device | Minimum height | Recommended height | Maximum height |
|---|---|---|---|
| Mobile | 40px | 48px | 56px |
| Tablet | 44px | 52px | 64px |
| Desktop | 48px | 56px | 72px |
Google Pay automatically displays button text in the user's preferred language. You can override this by setting the locale property:
style: {
type: 'buy',
color: 'default',
height: '48px',
locale: 'fr-FR' // Force French language
}The Google Pay button component provides event handlers to manage the complete payment flow:
const config = {
paymentDataRequest: {
// ... payment configuration
},
onGooglePaymentButtonClicked: async (event) => { },
onPreAuthorisation: async (data) => { return null; },
onPostAuthorisation: (result, paymentData) => { },
onPaymentDataChanged: async (intermediatePaymentData) => { return {}; },
onCustomValidation: async () => { return true; },
onError: (error) => { },
onCancel: () => { },
onGetConsent: () => { return false; }
};| Callback | Description |
|---|---|
onGooglePaymentButtonClicked: ((event: Event) => Promise<void>)? | Event handler for when the Google Pay button is clicked. Called before the payment sheet opens. |
onPreAuthorisation: ((data: PreAuthorizationData \| null) => Promise<GooglePayTransactionInitData \| null>)? | Event handler called before authorisation to provide additional transaction data like 3DS or risk screening. |
onPostAuthorisation: ((submitResult: BaseSubmitResult \| null, paymentData: AuthorisationPaymentData) => void)? | Event handler for when the payment authorisation completes. Receives transaction result and payment data. |
onPaymentDataChanged: (google.payments.api.PaymentDataChangedHandler)? | Event handler for dynamic updates to the payment sheet (shipping, pricing). See Payment sheet interactions below. |
onCustomValidation: (() => Promise<boolean>)? | Custom validation handler called before opening payment sheet. Return true to proceed. |
onError: ((error: BaseSdkException) => void)? | Event handler for when an error prevents the payment from proceeding. |
onCancel: (() => void)? | Event handler for when the user cancels the payment flow. |
onGetConsent: (() => boolean)? | Event handler to get user consent status for payment token storage. Return true if user consents. |
onPreRetrySoftDecline: ((result: BaseSubmitResult) => boolean \| object)? | Handler for retry logic after soft decline. Return true to retry or an object with retry settings. |
For detailed information about event data structures and usage patterns, see Events.
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 page reloads
- 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 callback intents in your payment request:
const googlePayConfig = {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'your-merchant-id'
}
}
}],
transactionInfo: {
currencyCode: 'GBP',
totalPriceStatus: 'FINAL',
totalPrice: '50.00',
displayItems: [
{
label: 'Subtotal',
type: 'LINE_ITEM',
price: '45.00'
},
{
label: 'Estimated Tax',
type: 'TAX',
price: '5.00'
}
]
},
// Enable callbacks for dynamic updates
callbackIntents: ['SHIPPING_ADDRESS', 'SHIPPING_OPTION'],
// Request shipping information
shippingAddressRequired: true,
shippingOptionRequired: true
},
// Handle payment data changes
onPaymentDataChanged: async (intermediatePaymentData) => {
// Dynamic update logic here
}
};| Intent | Description | When to use |
|---|---|---|
SHIPPING_ADDRESS | Called when shipping address changes | Calculate shipping costs and taxes based on location |
SHIPPING_OPTION | Called when shipping option changes | Update totals based on selected shipping method |
OFFER | Called when offer/promo code changes | Apply or validate promotional offers |
PAYMENT_AUTHORIZATION | Called before final authorisation | Perform final validation before payment |
The PAYMENT_AUTHORIZATION callback intent is automatically added by the SDK when using payment sheet interactions. You only need to specify SHIPPING_ADDRESS, SHIPPING_OPTION, or OFFER as needed.
Request shipping addresses by configuring the payment request:
const googlePayConfig = {
paymentDataRequest: {
// ... other configuration
// Enable shipping address collection
shippingAddressRequired: true,
// Configure shipping address parameters
shippingAddressParameters: {
allowedCountryCodes: ['GB', 'FR', 'DE', 'US'], // Restrict to specific countries
phoneNumberRequired: true // Request phone number
},
// Enable callback for address changes
callbackIntents: ['SHIPPING_ADDRESS']
},
onPaymentDataChanged: async (intermediatePaymentData) => {
const address = intermediatePaymentData.shippingAddress;
if (address) {
// Calculate shipping and update totals
return await handleShippingAddressChange(address);
}
return {};
}
};Respond to address changes with updated pricing and shipping options:
async function handleShippingAddressChange(address) {
console.log('Shipping address changed:', {
countryCode: address.countryCode,
postalCode: address.postalCode,
administrativeArea: address.administrativeArea
});
try {
// 1. Validate the address
const isValid = await validateShippingAddress(address);
if (!isValid) {
return {
error: {
reason: 'SHIPPING_ADDRESS_INVALID',
message: 'Please enter a complete address',
intent: 'SHIPPING_ADDRESS'
}
};
}
// 2. Check if we ship to this location
const canShip = await checkShippingAvailability(address.countryCode);
if (!canShip) {
return {
error: {
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
message: `We do not currently ship to ${address.countryCode}`,
intent: 'SHIPPING_ADDRESS'
}
};
}
// 3. Calculate shipping cost
const shippingCost = await calculateShipping(address);
// 4. Calculate tax
const subtotal = 45.00;
const tax = await calculateTax(address, subtotal);
// 5. Calculate new total
const newTotal = subtotal + tax + shippingCost;
// 6. Update SDK amount (synchronise with backend)
pxpSdk.updateAmount(newTotal);
// 7. Return updated transaction info
return {
newTransactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: newTotal.toFixed(2),
totalPriceLabel: 'Total',
displayItems: [
{
label: 'Subtotal',
type: 'LINE_ITEM',
price: subtotal.toFixed(2),
status: 'FINAL'
},
{
label: 'Shipping',
type: 'LINE_ITEM',
price: shippingCost.toFixed(2),
status: 'FINAL'
},
{
label: 'Tax',
type: 'TAX',
price: tax.toFixed(2),
status: 'FINAL'
}
]
}
};
} catch (error) {
console.error('Error handling address change:', error);
return {
error: {
reason: 'SHIPPING_ADDRESS_INVALID',
message: 'Unable to calculate shipping. Please try again.',
intent: 'SHIPPING_ADDRESS'
}
};
}
}// Validate address completeness
function validateShippingAddress(address) {
const requiredFields = ['countryCode', 'postalCode', 'administrativeArea'];
for (const field of requiredFields) {
if (!address[field]) {
console.error(`Missing required field: ${field}`);
return false;
}
}
// Validate postal code format
if (address.countryCode === 'GB') {
const ukPostcodeRegex = /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i;
if (!ukPostcodeRegex.test(address.postalCode)) {
console.error('Invalid UK postcode format');
return false;
}
}
return true;
}
// Check shipping availability
async function checkShippingAvailability(countryCode) {
const shippableCountries = ['GB', 'US', 'CA', 'FR', 'DE', 'ES', 'IT'];
return shippableCountries.includes(countryCode);
}
// Calculate shipping based on location
async function calculateShipping(address) {
const shippingRates = {
'GB': 4.99,
'US': 9.99,
'CA': 12.99,
'FR': 8.99,
'DE': 8.99,
'ES': 8.99,
'IT': 8.99
};
return shippingRates[address.countryCode] || 15.99; // Default international rate
}
// Calculate tax based on location
async function calculateTax(address, subtotal) {
const taxRates = {
'GB': 0.20, // 20% VAT
'US': 0.08, // Example sales tax
'CA': 0.13, // Example HST
'FR': 0.20, // 20% TVA
'DE': 0.19 // 19% MwSt
};
const rate = taxRates[address.countryCode] || 0;
return subtotal * rate;
}Provide multiple shipping methods for customers to choose from:
const googlePayConfig = {
paymentDataRequest: {
// ... other configuration
// Enable shipping option selection
shippingOptionRequired: true,
// Configure available shipping options
shippingOptionParameters: {
defaultSelectedOptionId: 'standard', // Pre-select an option
shippingOptions: [
{
id: 'standard',
label: 'Standard Delivery',
description: '5-7 business days'
},
{
id: 'express',
label: 'Express Delivery',
description: '2-3 business days'
},
{
id: 'next-day',
label: 'Next Day Delivery',
description: 'Order by 6pm for next day'
}
]
},
// Enable callback for option changes
callbackIntents: ['SHIPPING_OPTION']
},
onPaymentDataChanged: async (intermediatePaymentData) => {
const shippingOption = intermediatePaymentData.shippingOptionData;
if (shippingOption) {
return await handleShippingOptionChange(shippingOption);
}
return {};
}
};Update pricing when the customer selects a different shipping method:
async function handleShippingOptionChange(shippingOption) {
console.log('Shipping option changed:', shippingOption.id);
// Define shipping costs
const shippingCosts = {
'standard': 4.99,
'express': 9.99,
'next-day': 14.99
};
const subtotal = 45.00;
const tax = 9.00;
const shippingCost = shippingCosts[shippingOption.id] || 4.99;
// Calculate new total
const newTotal = subtotal + tax + shippingCost;
// Update SDK amount
pxpSdk.updateAmount(newTotal);
// Track shipping method selection
trackEvent('shipping-option-selected', {
optionId: shippingOption.id,
cost: shippingCost
});
return {
newTransactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: newTotal.toFixed(2),
totalPriceLabel: 'Total',
displayItems: [
{
label: 'Subtotal',
type: 'LINE_ITEM',
price: subtotal.toFixed(2)
},
{
label: shippingOption.label,
type: 'LINE_ITEM',
price: shippingCost.toFixed(2)
},
{
label: 'Tax',
type: 'TAX',
price: tax.toFixed(2)
}
]
}
};
}Request the customer's email address for order confirmations:
const googlePayConfig = {
paymentDataRequest: {
// ... other configuration
// Request email address
emailRequired: true
},
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
// Access email from payment data
const customerEmail = paymentData.Email;
// Send confirmation email
sendOrderConfirmation(customerEmail, {
orderId: result.merchantTransactionId,
systemTransactionId: result.systemTransactionId
});
}
}
};Request billing address for verification:
const googlePayConfig = {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
// Request billing address
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL', // or 'MIN' for minimal details
phoneNumberRequired: false
}
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'your-merchant-id'
}
}
}],
// ... rest of configuration
}
};| 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:
const googlePayButton = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
phoneNumberRequired: true
}
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'merchant-12345'
}
}
}],
transactionInfo: {
currencyCode: 'GBP',
countryCode: 'GB',
totalPriceStatus: 'FINAL',
totalPrice: '45.00',
totalPriceLabel: 'Total',
displayItems: [
{
label: 'Wireless Headphones',
type: 'LINE_ITEM',
price: '39.99',
status: 'FINAL'
},
{
label: 'Estimated Tax',
type: 'TAX',
price: '5.01',
status: 'PENDING'
}
]
},
// Enable all interactions
callbackIntents: ['SHIPPING_ADDRESS', 'SHIPPING_OPTION'],
// Email collection
emailRequired: true,
// Shipping address collection
shippingAddressRequired: true,
shippingAddressParameters: {
allowedCountryCodes: ['GB', 'FR', 'DE', 'ES', 'IT', 'NL', 'BE'],
phoneNumberRequired: true
},
// Shipping options
shippingOptionRequired: true,
shippingOptionParameters: {
defaultSelectedOptionId: 'standard',
shippingOptions: [
{
id: 'standard',
label: 'Standard Delivery',
description: '5-7 business days'
},
{
id: 'express',
label: 'Express Delivery',
description: '2-3 business days'
}
]
}
},
// Handle dynamic updates
onPaymentDataChanged: async (intermediatePaymentData) => {
console.log('Payment data changed:', {
callbackTrigger: intermediatePaymentData.callbackTrigger,
hasShippingAddress: !!intermediatePaymentData.shippingAddress,
hasShippingOption: !!intermediatePaymentData.shippingOptionData
});
try {
const subtotal = 39.99;
let shippingCost = 0;
let tax = 0;
let error = null;
// Handle shipping address change
if (intermediatePaymentData.shippingAddress) {
const address = intermediatePaymentData.shippingAddress;
// Validate address
if (!address.postalCode || !address.countryCode) {
error = {
reason: 'SHIPPING_ADDRESS_INVALID',
message: 'Please enter a complete address',
intent: 'SHIPPING_ADDRESS'
};
}
// Check if we ship to this country
else if (!['GB', 'FR', 'DE', 'ES', 'IT', 'NL', 'BE'].includes(address.countryCode)) {
error = {
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
message: `We do not ship to ${address.countryCode}`,
intent: 'SHIPPING_ADDRESS'
};
}
// Calculate shipping and tax
else {
const shippingRates = {
'GB': 4.99,
'FR': 8.99,
'DE': 8.99,
'ES': 8.99,
'IT': 8.99,
'NL': 8.99,
'BE': 8.99
};
shippingCost = shippingRates[address.countryCode] || 4.99;
// Calculate tax
const taxRates = {
'GB': 0.20,
'FR': 0.20,
'DE': 0.19,
'ES': 0.21,
'IT': 0.22,
'NL': 0.21,
'BE': 0.21
};
tax = subtotal * (taxRates[address.countryCode] || 0.20);
}
}
// Handle shipping option change
if (intermediatePaymentData.shippingOptionData) {
const optionId = intermediatePaymentData.shippingOptionData.id;
const shippingCosts = {
'standard': 4.99,
'express': 9.99
};
shippingCost = shippingCosts[optionId] || 4.99;
}
// Calculate new total
const newTotal = subtotal + tax + shippingCost;
// Update SDK amount
pxpSdk.updateAmount(newTotal);
// Return updated payment data
const response = {
newTransactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: newTotal.toFixed(2),
totalPriceLabel: 'Total',
displayItems: [
{
label: 'Wireless Headphones',
type: 'LINE_ITEM',
price: subtotal.toFixed(2),
status: 'FINAL'
},
{
label: 'Shipping',
type: 'LINE_ITEM',
price: shippingCost.toFixed(2),
status: 'FINAL'
},
{
label: 'VAT',
type: 'TAX',
price: tax.toFixed(2),
status: 'FINAL'
}
]
}
};
// Add error if present
if (error) {
response.error = error;
}
return response;
} catch (error) {
console.error('Error in onPaymentDataChanged:', error);
return {
error: {
reason: 'OTHER_ERROR',
message: 'An error occurred. Please try again.',
intent: 'SHIPPING_ADDRESS'
}
};
}
},
// Handle successful payment
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
console.log('Payment successful with collected data:', {
email: paymentData.Email,
shippingAddress: paymentData.ShippingAddress,
shippingOption: paymentData.ShippingOption
});
// Send confirmation email
sendOrderConfirmation(paymentData.Email, {
orderId: result.merchantTransactionId,
systemTransactionId: result.systemTransactionId,
shippingAddress: paymentData.ShippingAddress
});
window.location.href = '/order-confirmation';
}
}
});
googlePayButton.mount('google-pay-container');The Google Pay button component provides methods for lifecycle management.
Renders the Google Pay button in the specified container:
const googlePayButton = pxpSdk.create('google-pay-button', config);
googlePayButton.mount('google-pay-container');Removes the Google Pay button from the DOM:
googlePayButton.unmount();For complete lifecycle management details, see Implementation.
A straightforward implementation with essential configuration and error handling:
const googlePayButton = pxpSdk.create('google-pay-button', {
// Payment configuration
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'merchant-12345'
}
}
}],
transactionInfo: {
currencyCode: 'USD',
totalPriceStatus: 'FINAL',
totalPrice: '29.99',
totalPriceLabel: 'Total'
}
},
// Essential event handlers
onGooglePaymentButtonClicked: async (event) => {
console.log('Google Pay button clicked');
trackEvent('google_pay_clicked');
},
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
console.log('Payment successful:', result.merchantTransactionId);
window.location.href = '/order-confirmation';
} else if (result && 'errorCode' in result) {
console.error('Payment failed:', result.errorReason);
showError('Payment failed. Please try again.');
}
},
onError: (error) => {
console.error('Google Pay error:', error);
showError('Payment error. Please try again.');
},
onCancel: () => {
console.log('Payment cancelled');
showMessage('Payment was cancelled.');
}
});
googlePayButton.mount('google-pay-container');Implementation with custom styling for brand consistency:
const googlePayButton = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD'],
allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'merchant-12345'
}
}
}],
transactionInfo: {
currencyCode: 'GBP',
totalPriceStatus: 'FINAL',
totalPrice: '49.99'
}
},
// Custom styling
style: {
type: 'checkout',
color: 'black',
height: '56px',
width: '100%',
borderRadius: 12,
borderType: 'no_border',
sizeMode: 'fill',
locale: 'en-GB'
},
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
navigateToSuccess(result.merchantTransactionId);
} else if (result && 'errorCode' in result) {
handlePaymentError(result.errorReason);
}
}
});
googlePayButton.mount('google-pay-container');A comprehensive implementation with dynamic pricing, shipping, and complete event handling:
For detailed implementation of shipping address handling and dynamic pricing logic, see the Payment sheet interactions section above.
const googlePayButton = pxpSdk.create('google-pay-button', {
// Payment details with shipping
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['CRYPTOGRAM_3DS'],
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
phoneNumberRequired: true
}
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'enterprise-merchant-789'
}
}
}],
transactionInfo: {
currencyCode: 'USD',
totalPriceStatus: 'FINAL',
totalPrice: '129.99',
displayItems: [{
label: 'Subtotal',
type: 'SUBTOTAL',
price: '119.99'
}, {
label: 'Shipping',
type: 'LINE_ITEM',
price: '10.00'
}]
},
merchantInfo: {
merchantName: 'Enterprise Store',
merchantId: 'BCR2DN4TZ6...'
},
emailRequired: true,
shippingAddressRequired: true,
shippingAddressParameters: {
allowedCountryCodes: ['US', 'GB', 'CA'],
phoneNumberRequired: true
},
shippingOptionRequired: true,
shippingOptionParameters: {
defaultSelectedOptionId: 'standard',
shippingOptions: [{
id: 'standard',
label: 'Standard Shipping (5-7 days)',
description: 'Standard delivery',
price: '5.99'
}, {
id: 'express',
label: 'Express Shipping (2-3 days)',
description: 'Faster delivery',
price: '12.99'
}]
}
},
// Custom styling
style: {
type: 'buy',
color: 'default',
height: '52px',
width: '100%',
borderRadius: 8,
sizeMode: 'fill'
},
// Pre-authorisation with transaction data
onPreAuthorisation: async (data) => {
console.log('Preparing transaction...');
return {
riskScreeningData: {
performRiskScreening: true,
excludeDeviceData: false,
items: [{
price: 119.99,
quantity: 1,
category: 'electronics',
sku: 'PROD-001'
}]
},
psd2Data: {
scaExemption: 'LowValue'
}
};
},
// Dynamic payment sheet updates
onPaymentDataChanged: async (intermediatePaymentData) => {
const intent = intermediatePaymentData.callbackTrigger;
if (intent === 'SHIPPING_OPTION') {
const shippingOptionId = intermediatePaymentData.shippingOptionData?.id;
const shippingCost = shippingOptionId === 'express' ? 12.99 : 5.99;
const subtotal = 119.99;
const total = (subtotal + shippingCost).toFixed(2);
return {
newTransactionInfo: {
totalPriceStatus: 'FINAL',
totalPrice: total,
displayItems: [{
label: 'Subtotal',
type: 'SUBTOTAL',
price: subtotal.toFixed(2)
}, {
label: 'Shipping',
type: 'LINE_ITEM',
price: shippingCost.toFixed(2)
}]
}
};
}
if (intent === 'SHIPPING_ADDRESS') {
const country = intermediatePaymentData.shippingAddress?.countryCode;
// Validate shipping country
if (!['US', 'GB', 'CA'].includes(country)) {
return {
error: {
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
message: 'We do not ship to this country',
intent: 'SHIPPING_ADDRESS'
}
};
}
}
return {};
},
// Complete event handling
onGooglePaymentButtonClicked: async (event) => {
console.log('Google Pay button clicked');
trackAnalyticsEvent('google_pay_clicked', {
amount: 129.99,
currency: 'USD'
});
},
onPostAuthorisation: (result, paymentData) => {
console.log('Payment completed:', result);
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
const orderId = result.merchantTransactionId;
const email = paymentData.Email;
const shippingAddress = paymentData.ShippingAddress;
trackAnalyticsEvent('purchase_success', {
orderId,
amount: 129.99,
currency: 'USD'
});
// Create order with shipping details
createOrder({
orderId,
email,
shippingAddress,
amount: 129.99
});
showSuccessMessage('Payment successful!');
setTimeout(() => {
window.location.href = `/order-confirmation?id=${orderId}`;
}, 2000);
} else {
console.error('Payment failed:', result.errorReason);
trackAnalyticsEvent('purchase_failed', {
reason: result.errorReason
});
showErrorDialog('Payment failed: ' + result.errorReason);
}
},
onError: (error) => {
const errorMessage = error.ErrorCode === 'SDK_ERROR'
? 'Failed to load Google Pay. Please check your connection.'
: `Payment error: ${error.message}`;
console.error('Google Pay error:', error);
trackAnalyticsEvent('google_pay_error', {
errorCode: error.ErrorCode,
errorMessage: error.message
});
showErrorDialog(errorMessage);
},
onCancel: () => {
console.log('Payment cancelled');
trackAnalyticsEvent('google_pay_cancelled');
showMessage('Payment was cancelled.');
}
});
googlePayButton.mount('google-pay-container');Implementation with consent component for saving payment methods:
// Create consent component first
const consentConfig = {
label: 'Save my payment method for future purchases',
checked: false
};
const consentComponent = pxpSdk.create('google-pay-consent', consentConfig);
consentComponent.mount('consent-container');
// Create Google Pay button with linked consent
const googlePayButton = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD'],
allowedAuthMethods: ['CRYPTOGRAM_3DS']
},
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
gatewayMerchantId: 'merchant-12345'
}
}
}],
transactionInfo: {
currencyCode: 'USD',
totalPriceStatus: 'FINAL',
totalPrice: '9.99',
totalPriceLabel: 'Monthly Subscription'
}
},
// Link consent component
googlePayConsentComponent: consentComponent,
// Alternative: Use onGetConsent callback
onGetConsent: () => {
const hasConsent = consentComponent.getValue() as boolean;
console.log('User consent:', hasConsent);
return hasConsent;
},
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
const hasConsent = consentComponent.getValue() as boolean;
if (hasConsent) {
console.log('Payment method saved for recurring payments');
savePaymentToken(result.merchantTransactionId);
}
window.location.href = '/subscription-active';
}
},
onError: (error) => {
console.error('Subscription error:', error);
showError('Unable to start subscription. Please try again.');
}
});
googlePayButton.mount('google-pay-container');The google-pay-consent component is an optional checkbox for collecting customer consent to store payment methods. It can be linked to the Google Pay button for use cases like recurring payments, subscriptions, or express checkout.
The consent component is separate from recurring payment configuration. Recurring payments are enabled by adding recurring configuration to transactionData in SDK initialization. See Recurring payments for details.
const consentComponent = pxpSdk.create('google-pay-consent', {
label: 'Save my payment method for future purchases',
checked: false
});
consentComponent.mount('consent-container');const consentComponent = pxpSdk.create('google-pay-consent', {
// Basic configuration
label: 'I agree to save my payment method for faster checkout',
checked: false,
// Visual configuration
checkedColor: '#4CAF50',
tabIndex: 5,
// Label styling
labelStyles: {
checked: {
color: '#4CAF50',
fontWeight: '600',
fontSize: '14px'
},
unchecked: {
color: '#666666',
fontSize: '14px',
fontWeight: '400'
}
}
});| Property | Description |
|---|---|
labelstring | Text label displayed next to the checkbox. Defaults to 'I agree to store my card information for faster checkout in the future'. |
checkedboolean | Initial checked state of the checkbox. Defaults to false. |
checkedColorstring | Custom colour for the checkbox when checked (e.g., '#4CAF50'). Defaults to '#292CF5'. |
tabIndexnumber | Tab index for keyboard navigation. Defaults to null. |
labelStylesobject | Custom styles for the label in different states. Defaults to null. |
labelStyles.checkedCSSProperties | Styles applied to the label when checkbox is checked. Defaults to null. |
labelStyles.uncheckedCSSProperties | Styles applied to the label when checkbox is unchecked. Defaults to null. |
Link the consent component to your Google Pay button:
// Create consent component
const consentComponent = pxpSdk.create('google-pay-consent', {
label: 'Save my payment method',
checked: false
});
// Create Google Pay button with consent reference
const googlePayButton = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
// ... payment configuration
},
// Link the consent component
googlePayConsentComponent: consentComponent,
onPostAuthorisation: async (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
console.log('Payment successful');
// Store payment token if consent was given
}
}
});
// Mount both components
consentComponent.mount('consent-container');
googlePayButton.mount('google-pay-container');Alternatively, use the onGetConsent callback if managing consent state separately:
const googlePayButton = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
// ... payment configuration
},
// Simple boolean consent callback
onGetConsent: () => {
// Return true if customer has given consent
return document.getElementById('my-consent-checkbox').checked;
},
onPostAuthorisation: async (result, paymentData) => {
// Handle payment based on consent
}
});When both googlePayConsentComponent and onGetConsent are provided, the consent component takes priority. The onGetConsent callback will not be called if a consent component is linked.
Customize the appearance for different states:
const consentComponent = pxpSdk.create('google-pay-consent', {
label: 'Save my payment method',
checkedColor: '#FF5722', // Custom brand color
labelStyles: {
checked: {
color: '#2E7D32',
fontWeight: 'bold',
fontSize: '16px',
textDecoration: 'underline'
},
unchecked: {
color: '#757575',
fontWeight: 'normal',
fontSize: '14px'
}
}
});The main configuration interface for the Google Pay button component:
interface GooglePayButtonComponentConfig extends BaseComponentConfig {
paymentDataRequest: GooglePayPaymentDataRequest;
style?: GooglePayButtonStyle;
existingPaymentMethodRequired?: boolean;
collectCvc?: 'always' | 'never' | 'default';
cvcVerificationPopupConfig?: CvcVerificationPopupConfig;
googlePayConsentComponent?: GooglePayConsentComponent;
onGetConsent?: () => boolean;
onPreAuthorisation?: (data: PreAuthorizationData | null) => Promise<GooglePayTransactionInitData | null>;
onPostAuthorisation?: (submitResult: BaseSubmitResult | null, paymentData: AuthorisationPaymentData) => void;
onPaymentDataChanged?: google.payments.api.PaymentDataChangedHandler;
onGooglePaymentButtonClicked?: (event: Event) => Promise<void>;
onCustomValidation?: () => Promise<boolean>;
onPreRetrySoftDecline?: (result: BaseSubmitResult) => boolean | {
retry: boolean;
updatedConfigs?: object;
};
onError?: (error: BaseSdkException) => void;
onCancel?: () => void;
}Configuration interface for the consent component:
interface GooglePayConsentComponentConfig extends BaseComponentConfig {
label?: string;
checked?: boolean;
checkedColor?: string;
tabIndex?: number;
labelStyles?: {
checked?: CSSProperties;
unchecked?: CSSProperties;
};
}Styling configuration interface:
interface GooglePayButtonStyle {
type?: 'buy' | 'book' | 'checkout' | 'donate' | 'order' | 'pay' | 'subscribe' | 'plain';
color?: 'default' | 'black' | 'white';
height?: string;
width?: string;
borderRadius?: number;
borderType?: 'default_border' | 'no_border';
sizeMode?: 'fill' | 'static';
buttonRootNode?: HTMLDocument | ShadowRoot | undefined;
locale?: string;
}