Learn about 3D Secure authentication with Google Pay for enhanced transaction security.
By implementing 3DS authentication with Google Pay, you benefit from:
- Enhanced security: Additional authentication layer protects against fraudulent transactions.
- Liability shift: Shifts liability from merchant to card issuer for authenticated transactions.
- Reduced chargebacks: Significantly decreases chargeback rates through strong authentication.
- Regulatory compliance: Meets Strong Customer Authentication (SCA) requirements under PSD2 and similar regulations.
- Higher success rate: Banks are more likely to approve 3DS-authenticated transactions.
- Fraud prevention: Real-time risk assessment and challenge when necessary.
However, the 3DS payment flow is longer than the non-3DS one due to the additional authentication steps. It may also require active customer participation if a challenge is presented.
3D Secure authentication is strongly recommended for high-value transactions, subscription services, and any scenario where enhanced security is required. It significantly reduces fraud risk and chargebacks whilst providing liability shift benefits.
3DS is applied only to Google Pay FPAN (Full Primary Account Number). DPAN (Device Primary Account Number) transactions from Google Pay do not undergo 3DS authentication.
The 3DS Google Pay payment flow consists of nine key steps.
The customer clicks the Google Pay button, triggering the payment sheet to open. Google Pay handles device authentication (biometric or PIN) internally.
Within the Google Pay payment sheet, the customer selects their preferred payment method. Google Pay tokenises the payment data using its secure tokenisation system.
The customer confirms the payment in the Google Pay sheet. The SDK receives the encrypted payment token from Google Pay.
This is the initial step in the 3DS authentication flow. It establishes the authentication session by sending transaction and card details to the payment processor.
This step has two associated callbacks:
onPreInitiateAuthentication: Returns configuration for the authentication setup.onPostInitiateAuthentication: Receives the result of the pre-initiation call.
During this step, device information and browser characteristics are collected by the fingerprinting component. It creates a hidden iframe that submits transaction details to the issuer's fingerprint URL, enabling risk assessment based on the user's device profile.
The 3DS server evaluates the transaction risk and determines the authentication path:
- Frictionless flow: If the transaction is low-risk, authentication completes automatically without customer interaction.
- Challenge flow: If additional verification is needed, the customer completes the 3DS authentication challenge (PIN entry, SMS code, biometric verification, etc.)
The authentication step has two associated callbacks:
onPreAuthentication: Configures the main authentication parameters.onPostAuthentication: Receives authentication results and challenge data.
The SDK receives the 3DS authentication result indicating whether authentication was successful, failed, or requires additional action.
This is the final processing step where the SDK sends the authorisation request to the payment gateway, including the 3DS authentication data. You receive the transaction data along with the 3DS authentication results and decide whether to proceed.
The authorisation step has two associated callbacks:
onPreAuthorisation: Provides final transaction data, including 3DS authentication results. This is your last chance to modify the transaction before authorisation.onPostAuthorisation: Receives the transaction identifiers (merchantTransactionIdandsystemTransactionId). Use these identifiers to call the Unity Backend and retrieve the full transaction outcome.
The onPostAuthorisation callback only returns transaction identifiers, not the full transaction result. To obtain the complete transaction outcome (Authorised, Captured, Refused, etc.), you must call the Unity Backend using the Get transaction details API with the systemTransactionId.
You receive the final authorisation response from the payment gateway. The transaction is either approved or declined and final transaction details are available, along with 3DS authentication confirmation.
To use 3DS with Google Pay payments:
- Ensure 3D Secure is enabled in the Unity Portal for your merchant group.
- Configure Google Pay in Unity Portal with your gateway merchant ID.
- Whitelist your domain in Unity Portal.
- Get your 3DS provider ID from your payment processor.
Initialise the PXP SDK with your merchant credentials.
import { PxpCheckout } from '@pxpio/web-components-sdk';
const pxpSdk = PxpCheckout.initialize({
environment: 'test', // or 'live' for production
session: sessionData, // Get from your backend
ownerId: 'your-owner-id',
ownerType: 'MerchantGroup',
kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
transactionData: {
currency: 'GBP',
amount: 299.99,
merchantTransactionId: crypto.randomUUID(),
merchantTransactionDate: () => new Date().toISOString(),
entryType: 'Ecom',
intent: {
card: 'Authorisation'
}
}
});Configure the Google Pay button with the required callbacks for 3DS authentication.
const googlePayButton = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['CRYPTOGRAM_3DS'] // Prefer 3DS cryptogram
}
// Note: tokenizationSpecification is automatically configured by the SDK from session data
}],
transactionInfo: {
currencyCode: 'GBP',
totalPriceStatus: 'FINAL',
totalPrice: '299.99'
}
},
// OPTIONAL: Use Unity authentication strategy
useUnityAuthenticationStrategy: true,
// REQUIRED: Provide 3DS configuration
onPreInitiateAuthentication: () => {
return {
providerId: 'your_3ds_provider_id', // optional
requestorAuthenticationIndicator: '01', // Payment transaction
timeout: 300 // 5 minutes
};
},
// OPTIONAL: Handle the pre-initiation result
onPostInitiateAuthentication: async (data) => {
console.log('3DS pre-initiation completed. Authentication ID:', data.authenticationId);
// Retrieve initiate authentication result from backend
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
// Evaluate the result to update authentication decision
await evaluateAuthenticationAndUpdateSession(authResult);
},
// REQUIRED: Configure main authentication
onPreAuthentication: async () => {
// Get authentication decision evaluated after onPostInitiateAuthentication
const decision = await getAuthenticationDecision();
if (!decision) {
// Not proceeding with authentication
return null;
}
return {
merchantCountryNumericCode: '826', // United Kingdom
merchantLegalName: 'Your Store Limited',
challengeWindowSize: 5, // Fullscreen
requestorChallengeIndicator: decision.challengeIndicator || '01',
timeout: decision.timeout || 300
};
},
// OPTIONAL: Handle the authentication result
onPostAuthentication: async (data) => {
console.log('3DS authentication completed. Authentication ID:', data.authenticationId);
// Send authenticationId to backend to retrieve authentication result
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
// Evaluate authentication result to update authorisation decision
await evaluateAuthenticationAndUpdateAuthorization(authResult);
},
// REQUIRED: Final transaction review before authorisation
onPreAuthorisation: async (data) => {
console.log('Processing 3DS Google Pay payment');
console.log('Gateway Token ID:', data.gatewayTokenId);
// Retrieve token details and make authorisation decision
const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
if (!transactionDecision) {
// Not proceeding
return null;
}
// Add any additional data
return {
riskScreeningData: {
performRiskScreening: true,
customData: {
processingType: '3ds',
customerType: 'returning'
}
}
};
},
// REQUIRED: Handle the final result
onPostAuthorisation: (result, paymentData) => {
console.log('Payment result:', result);
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
console.log('Payment successful with 3DS!');
console.log('Transaction ID:', result.merchantTransactionId);
console.log('System Transaction ID:', result.systemTransactionId);
// Redirect to success page
window.location.href = '/payment-success?txn=' + result.merchantTransactionId;
} else if (result && 'errorCode' in result) {
// Failure - FailedSubmitResult
console.error('Payment declined:', result.errorReason);
showError('Payment declined. Please try another payment method.');
}
},
// OPTIONAL: Handle errors
onError: (error) => {
console.error('Payment error:', error);
showError('An error occurred. Please try again.');
},
// OPTIONAL: Handle cancellation
onCancel: () => {
console.log('Payment cancelled by user');
showMessage('Payment cancelled');
}
});Use the following snippet to only trigger 3DS authentication above a certain amount.
const googlePayButton = pxpSdk.create('google-pay-button', {
// ... basic configuration
onPreInitiateAuthentication: () => {
const amount = getTransactionAmount();
if (amount > 100) {
return {
providerId: 'your_provider',
requestorAuthenticationIndicator: '01',
timeout: 300
};
}
// Return null to skip 3DS for low-value transactions
return null;
}
});Select appropriate challenge indicators based on transaction risk.
function selectChallengeIndicator(transaction) {
// High-value transactions
if (transaction.amount > 500) {
return '04'; // Mandate challenge
}
// Subscription first payment
if (transaction.type === 'subscription' && transaction.isFirstPayment) {
return '03'; // Challenge requested (merchant preference)
}
// Trusted returning customers
if (transaction.customerTrust === 'HIGH') {
return '02'; // No challenge requested
}
// Default
return '01'; // No preference
}
const googlePayButton = pxpSdk.create('google-pay-button', {
// ... basic configuration
onPreAuthentication: async () => {
const decision = await getAuthenticationDecision();
if (!decision) {
return null;
}
const transaction = getCurrentTransaction();
const challengeIndicator = selectChallengeIndicator(transaction);
return {
merchantCountryNumericCode: '826',
merchantLegalName: 'Your Store Ltd',
challengeWindowSize: 5,
requestorChallengeIndicator: challengeIndicator
};
}
});Handle soft declines by retrying with forced 3DS challenge.
const googlePayButton = pxpSdk.create('google-pay-button', {
// ... other configuration
onPreRetrySoftDecline: (result) => {
console.log('Soft decline detected, retrying with 3DS challenge');
// Track soft decline
trackEvent('payment-soft-decline', {
transactionId: result.merchantTransactionId,
reason: result.errorReason
});
// Retry with forced 3DS challenge
return {
retry: true,
updatedConfigs: {
onPreInitiateAuthentication: () => ({
providerId: 'pxpfinancial',
timeout: 12
}),
onPreAuthentication: async () => ({
merchantCountryNumericCode: '826',
merchantLegalName: 'Your Store Ltd',
challengeWindowSize: 5,
requestorChallengeIndicator: '04' // Force challenge on retry
})
}
};
}
});Implement comprehensive error handling for 3DS payments.
const googlePayButton = pxpSdk.create('google-pay-button', {
// ... basic configuration
onPostAuthentication: async (data) => {
// Retrieve authentication result from backend
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
// Handle authentication failures
if (authResult.state === 'AuthenticationFailed') {
console.log('Authentication failed:', authResult.errorReason);
showError('Card authentication failed');
// Don't proceed to authorisation - update session accordingly
await updateSessionDecision(data.authenticationId, { proceed: false });
return;
}
console.log('Authentication successful, proceeding to payment');
// Update authorization decision
await evaluateAuthenticationAndUpdateAuthorization(authResult);
},
onError: (error) => {
console.error('Payment error:', error);
// Handle specific 3DS error types
if (error.code === 'AUTHENTICATION_FAILED') {
showError('Card verification failed. Please try again.');
} else if (error.code === 'AUTHENTICATION_TIMEOUT') {
showError('Verification timed out. Please try again.');
} else if (error.code === 'AUTHENTICATION_REJECTED') {
showError('Payment was rejected during verification.');
} else if (error.code === 'BUYER_ACCOUNT_ERROR') {
showError('There was an issue with your Google Pay account. Please try another payment method.');
} else if (error.code === 'MERCHANT_CONFIGURATION_ERROR') {
showError('Payment system configuration error. Please contact support.');
logError('Google Pay merchant configuration error', error);
} else if (error.code === 'NETWORK_ERROR') {
showError('Connection failed. Please check your internet and try again.');
} else {
showError('Payment failed. Please try again or use a different payment method.');
}
},
onPostAuthorisation: (result, paymentData) => {
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
handlePaymentSuccess(result, paymentData);
} else if (result && 'errorCode' in result) {
// Failure - FailedSubmitResult
handlePaymentFailure(result);
}
}
});
function handlePaymentSuccess(result, paymentData) {
console.log('✅ Payment successful with 3DS');
console.log('Transaction ID:', result.merchantTransactionId);
console.log('System Transaction ID:', result.systemTransactionId);
// Store transaction details
storeTransactionRecord({
transactionId: result.merchantTransactionId,
systemTransactionId: result.systemTransactionId,
processingType: '3ds',
paymentMethod: 'google-pay',
timestamp: new Date().toISOString()
});
// Redirect to success page
window.location.href = '/payment-success?txn=' + result.merchantTransactionId;
}
function handlePaymentFailure(result) {
console.error('❌ Payment failed');
console.error('Error code:', result.errorCode);
console.error('Error reason:', result.errorReason);
console.error('Correlation ID:', result.correlationId);
// Map error codes to user-friendly messages
const errorMessages = {
'INSUFFICIENT_FUNDS': 'Insufficient funds. Please try a different payment method.',
'CARD_DECLINED': 'Payment declined by your bank. Please try another card.',
'EXPIRED_CARD': 'This card has expired. Please use a different payment method.',
'INVALID_CARD': 'Invalid card details. Please try another payment method.',
'FRAUD_DETECTED': 'Payment could not be processed. Please contact your bank.',
'PROCESSING_ERROR': 'Payment processing error. Please try again.',
'LIMIT_EXCEEDED': 'Transaction exceeds your card limit. Try a smaller amount.',
'DO_NOT_HONOR': 'Payment declined by your bank. Please try a different payment method.'
};
const userMessage = errorMessages[result.errorCode] ||
'Payment could not be completed. Please try a different payment method.';
showError(userMessage);
// Log for analysis
logPaymentDecline({
errorCode: result.errorCode,
errorReason: result.errorReason,
correlationId: result.correlationId,
httpStatusCode: result.httpStatusCode,
timestamp: new Date().toISOString()
});
// Offer alternative payment methods
showAlternativePaymentOptions();
}The following example shows a complete 3DS Google Pay implementation.
import { useEffect, useState } from 'react';
function GooglePay3DSCheckout() {
const [googlePayButton, setGooglePayButton] = useState(null);
const [isProcessing, setIsProcessing] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
// Initialise SDK
const pxpSdk = PxpCheckout.initialize({
environment: 'test',
session: sessionData, // Get from your backend
ownerId: 'your-owner-id',
ownerType: 'MerchantGroup',
kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
transactionData: {
currency: 'GBP',
amount: 299.99,
merchantTransactionId: crypto.randomUUID(),
merchantTransactionDate: () => new Date().toISOString(),
entryType: 'Ecom',
intent: {
card: 'Authorisation'
}
}
});
// Create Google Pay button with 3DS
const button = pxpSdk.create('google-pay-button', {
paymentDataRequest: {
allowedPaymentMethods: [{
type: 'CARD',
parameters: {
allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
allowedAuthMethods: ['CRYPTOGRAM_3DS']
}
// Note: tokenizationSpecification is automatically configured by the SDK from session data
}],
transactionInfo: {
currencyCode: 'GBP',
countryCode: 'GB',
totalPriceStatus: 'FINAL',
totalPrice: '299.99'
}
},
// Use Unity authentication strategy
useUnityAuthenticationStrategy: true,
// Step 1: Set up 3DS
onPreInitiateAuthentication: () => {
console.log('🔐 Initiating 3DS authentication');
return {
providerId: process.env.REACT_APP_THREEDS_PROVIDER_ID,
requestorAuthenticationIndicator: '01',
timeout: 300
};
},
// Step 2: Handle pre-initiation result
onPostInitiateAuthentication: async (data) => {
console.log('📋 Pre-authentication check complete:', data.authenticationId);
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
await evaluateAuthenticationAndUpdateSession(authResult);
if (authResult.scaMandated) {
console.log('SCA mandated - full authentication required');
}
},
// Step 3: Configure authentication
onPreAuthentication: async () => {
console.log('🔍 Configuring full 3DS authentication');
const decision = await getAuthenticationDecision();
if (!decision) {
return null; // Skip authentication
}
// Assess transaction risk
const amount = getTransactionAmount();
const customerHistory = await getCustomerHistory();
const isHighRisk = amount > 500 || customerHistory.chargebacks > 0;
let challengeIndicator = '01'; // No preference
if (isHighRisk) {
challengeIndicator = '04'; // Mandate challenge
} else if (amount < 30 && customerHistory.successfulTransactions > 10) {
challengeIndicator = '02'; // No challenge requested
}
return {
merchantCountryNumericCode: '826',
merchantLegalName: 'Your Store Limited',
challengeWindowSize: 5,
requestorChallengeIndicator: challengeIndicator
};
},
// Step 4: Handle authentication result
onPostAuthentication: async (data) => {
console.log('✅ 3DS authentication completed:', data.authenticationId);
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
if (authResult.state === 'AuthenticationFailed') {
setError('Card verification failed. Please try again.');
return;
}
await evaluateAuthenticationAndUpdateAuthorization(authResult);
},
// Step 5: Final authorisation
onPreAuthorisation: async (data) => {
console.log('💳 Processing payment with 3DS data');
const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
if (!transactionDecision) {
return null;
}
return {
riskScreeningData: {
performRiskScreening: true,
customData: {
threeDsApplied: true,
paymentMethod: 'google-pay'
}
}
};
},
// Step 6: Handle success/failure
onPostAuthorisation: (result, paymentData) => {
setIsProcessing(false);
if (result && 'merchantTransactionId' in result) {
console.log('✅ Payment authorised with 3DS');
window.location.href = '/payment-success?txn=' + result.merchantTransactionId;
} else if (result && 'errorCode' in result) {
console.error('❌ Payment declined:', result.errorReason);
setError('Payment declined. Please try another payment method.');
}
},
// Step 7: Error handling
onError: (error) => {
console.error('Error during 3DS flow:', error);
setIsProcessing(false);
if (error.code === 'AUTHENTICATION_FAILED') {
setError('Card verification failed. Please try again.');
} else if (error.code === 'AUTHENTICATION_TIMEOUT') {
setError('Verification timed out. Please try again.');
} else {
setError('An error occurred. Please try again.');
}
},
onCancel: () => {
console.log('Payment cancelled by user');
setIsProcessing(false);
setError(null);
}
});
// Mount button
button.mount('google-pay-button-container');
setGooglePayButton(button);
// Cleanup
return () => {
if (button) {
button.unmount();
}
};
}, []);
return (
<div className="checkout-container">
<div className="order-summary">
<h2>Order summary</h2>
<div className="order-items">
<div className="order-item">
<span>Premium Product</span>
<span>£299.99</span>
</div>
<div className="order-total">
<span>Total</span>
<span>£299.99</span>
</div>
</div>
</div>
<div className="payment-section">
<h2>Pay with Google Pay (3DS Secure)</h2>
{error && (
<div className="error-message">
{error}
</div>
)}
<div id="google-pay-button-container"></div>
{isProcessing && (
<div className="processing-indicator">
Processing secure payment...
</div>
)}
<div className="payment-info">
<p>✓ Protected by 3D Secure authentication</p>
<p>✓ Fast and secure checkout with Google Pay</p>
<p>✓ Additional verification for your safety</p>
</div>
</div>
</div>
);
}
export default GooglePay3DSCheckout;This section describes the data received by the different callbacks as part of the 3DS Google Pay flow.
Note that the onPreInitiateAuthentication callback doesn't receive anything so isn't included. Instead, it returns your 3DS configuration in the PreInitiateIntegratedAuthenticationData object.
The onPostInitiateAuthentication callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.
| Parameter | Description |
|---|---|
dataobject | Object containing the authentication initiation result. |
data.authenticationIdstring | The unique identifier for this 3DS session. Use this ID to retrieve full PreInitiateAuthentication result from Unity backend. |
Use the authenticationId with the Get 3DS pre-initiate authentication details API to retrieve pre-authentication results including SCA mandates, exemptions, and 3DS support.
onPostInitiateAuthentication: async (data) => {
console.log('3DS pre-initiation completed. Authentication ID:', data.authenticationId);
// Retrieve initiate authentication result from Unity
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
// Check if authentication setup was successful
if (authResult.state === 'PendingClientData') {
console.log('Waiting for client data collection');
}
// Check if SCA (Strong Customer Authentication) is mandated
if (authResult.scaMandated) {
console.log('SCA is required - 3DS must complete successfully');
showMessage('Additional verification required');
}
// Check available exemptions
if (authResult.applicableExemptions === 'LVP') {
console.log('Low value exemption available');
} else if (authResult.applicableExemptions === 'TRA') {
console.log('Transaction risk analysis exemption available');
}
// Evaluate the result to update authentication decision
const decision = await evaluateAuthenticationAndUpdateSession(authResult);
}The onPreAuthentication callback receives no parameters. You should use the authentication decision evaluated after onPostInitiateAuthentication to determine whether to proceed.
This callback receives no parameters.
onPreAuthentication: async () => {
// Get authentication decision evaluated after onPostInitiateAuthentication
const decision = await getAuthenticationDecision();
if (!decision) {
// Not proceeding with authentication
console.log('Skipping authentication based on backend decision');
return null;
}
console.log('Proceeding with authentication');
// Check if SCA is mandated to adjust challenge preference
let challengeIndicator = '01'; // No preference
if (decision.scaMandated) {
challengeIndicator = '04';
console.log('SCA mandated - requesting challenge');
} else if (decision.applicableExemptions === 'LVP') {
challengeIndicator = '10';
console.log('Low value exemption - requesting no challenge');
} else if (decision.applicableExemptions === 'TRA') {
challengeIndicator = '05';
console.log('TRA exemption - requesting no challenge');
}
return {
merchantCountryNumericCode: '826',
merchantLegalName: 'Your Company Ltd',
challengeWindowSize: 5,
requestorChallengeIndicator: challengeIndicator,
timeout: decision.scaMandated ? 600 : 300
};
}| Value | Dimensions | Description |
|---|---|---|
1 | 250x400 | Compact window |
2 | 390x400 | Standard window |
3 | 500x600 | Medium window |
4 | 600x400 | Wide window |
5 | Fullscreen | Full-screen window |
| Value | Description | When to use |
|---|---|---|
01 | No preference | Standard transactions |
02 | No challenge requested | Low-risk transactions |
03 | Challenge requested: Your preference | When additional security desired |
04 | Challenge requested: Mandate | High-value or high-risk transactions |
05 | No challenge requested (transaction risk analysis performed) | When you have performed risk analysis |
06 | No challenge requested (Data share only) | Token provisioning |
07 | No challenge requested (SCA already performed) | Subsequent transactions |
08 | No challenge requested (Trust list exemption) | Whitelist transactions |
09 | Challenge requested (Trust list prompt) | To add you to whitelist |
10 | No challenge requested (Low value exemption) | Low-value transactions under exemption thresholds |
11 | No challenge requested (Secure corporate payment) | Corporate card payments |
12 | Challenge requested (Device binding prompt) | Device binding scenarios |
13 | Challenge requested (Issuer requested) | When issuer specifically requires challenge |
14 | Challenge requested (Your initiated transactions) | MIT scenarios requiring challenge |
The onPostAuthentication callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.
| Parameter | Description |
|---|---|
dataobject | Object containing authentication result. |
data.authenticationIdstring | The unique identifier for the authentication attempt. Use this ID to retrieve full authentication details from the Unity backend. |
Use the authenticationId with the Get 3DS authentication details API to retrieve the full authentication results including transaction status, ECI values, and CAVV data.
onPostAuthentication: async (data) => {
console.log('3DS authentication completed. Authentication ID:', data.authenticationId);
// Send authenticationId to backend to retrieve authentication result
const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
console.log('Authentication result:', authResult);
// Check transaction status
switch (authResult.transactionStatus) {
case 'Y':
console.log('Authentication successful - no challenge needed');
showMessage('Card verified successfully');
break;
case 'C':
console.log('Challenge completed - checking result...');
if (authResult.state === 'AuthenticationSuccessful') {
console.log('Challenge completed successfully');
showMessage('Verification completed');
} else {
console.log('Challenge failed');
showError('Verification failed. Please try again.');
return; // Stop payment
}
break;
case 'N':
console.log('Authentication failed');
showError('Card verification failed');
return; // Stop payment
case 'R':
console.log('Authentication rejected');
showError('Payment was rejected by your bank');
return; // Stop payment
}
// Evaluate authentication result to update authorisation decision
const authorisationDecision = await evaluateAuthenticationAndUpdateAuthorization(authResult);
console.log('Proceeding to final authorisation...');
}The onPreAuthorisation callback receives only the gateway token ID. Use this ID to retrieve token details and make authorisation decisions on your backend.
| Parameter | Description |
|---|---|
dataobject | Object containing token information. |
data.gatewayTokenIdstring | The token ID from the payment gateway. Use this ID to retrieve full token details from Unity backend and update transaction decision. |
Use the gatewayTokenId with the Get masked card data related to gateway token API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.
onPreAuthorisation: async (data) => {
console.log('Pre-authorisation for token:', data.gatewayTokenId);
// Retrieve token details and make authorisation decision
const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
if (!transactionDecision) {
// Not proceeding
console.log('Not proceeding with authorization');
return null;
}
// Perform pre-payment validation
const isHighRisk = await checkCustomerRiskProfile();
const customerTier = await getCustomerTier();
// Get billing address if AVS is enabled
const billingAddress = await getBillingAddress();
return {
addressVerification: billingAddress ? {
countryCode: billingAddress.countryCode,
houseNumberOrName: billingAddress.address,
postalCode: billingAddress.postalCode,
city: billingAddress.city,
state: billingAddress.state
} : undefined,
riskScreeningData: {
performRiskScreening: true,
customData: {
customerTier: customerTier,
orderType: 'ecommerce',
paymentMethod: 'google-pay',
threeDsApplied: true,
previousTransactionCount: await getPreviousTransactionCount(),
riskScore: isHighRisk ? 'high' : 'low'
}
}
};
}Important: 3DS external data (obtained from external authentication sources) should no longer be provided via the threeDSecureData return parameter. Instead, provide this data to the backend via the Unity session update endpoint.
When you retrieve the full token/transaction details from your backend, you'll get detailed information including 3DS authentication results.
{
initiateIntegratedSuccessAuthenticationResult: {
status: 200,
authenticationId: "auth_12345678-abcd-1234-efgh-123456789012",
uniqueId: "unique_98765",
state: "Completed",
transactionStatus: "Y",
electronicCommerceIndicator: "05",
exemptionGranted: false,
exemptionGrantedByIssuer: "79",
threeDSecureVersion: "2.2.0",
directoryServerTransactionId: "ds_trans_12345",
cardholderAuthenticationVerificationValue: "jGvQIvG/5UhjAREALGYYemQLXPI=",
acsUrl: null,
challengeData: null,
stateData: {
code: "SUCCESS",
reason: "Authentication completed successfully"
},
cardholderInfo: "Additional cardholder verification completed"
},
transactionInitiationData: {
psd2Data: {
scaExemption: null
},
threeDSecureData: {
threeDSecureVersion: "2.2.0",
electronicCommerceIndicator: "05",
cardHolderAuthenticationVerificationValue: "jGvQIvG/5UhjAREALGYYemQLXPI=",
directoryServerTransactionId: "ds_trans_12345",
threeDSecureTransactionStatus: "Y"
},
identityVerification: {
nameVerification: true
},
addressVerification: {
countryCode: "GB",
houseNumberOrName: "123",
postalCode: "SW1A 1AA"
}
},
cardTokenData: null
}| Parameter | Description |
|---|---|
initiateIntegratedSuccessAuthenticationResultobject | Details about the successful 3DS authentication. |
initiateIntegratedSuccessAuthenticationResult.statusnumber | The HTTP status code. |
initiateIntegratedSuccessAuthenticationResult.authenticationIdstring | The unique identifier for this 3DS session. |
initiateIntegratedSuccessAuthenticationResult.uniqueIdstring | The unique identifier for this authentication attempt. |
initiateIntegratedSuccessAuthenticationResult.statestring | The state of the authentication. |
initiateIntegratedSuccessAuthenticationResult.transactionStatusstring | The status of the transaction. Possible values: * Y - Authentication verification successful* N - Not authenticated* U - Authentication couldn't be performed* A - Attempts processing performed* C - Challenge required* R - Authentication rejected |
initiateIntegratedSuccessAuthenticationResult.electronicCommerceIndicatorstring | The Electronic Commerce Indicator (ECI). |
initiateIntegratedSuccessAuthenticationResult.exemptionGrantedboolean | Whether an exemption was granted. |
initiateIntegratedSuccessAuthenticationResult.exemptionGrantedByIssuerstring | The type of exemption granted by the issuer. |
initiateIntegratedSuccessAuthenticationResult.threeDSecureVersionstring | The 3DS version used. |
initiateIntegratedSuccessAuthenticationResult.directoryServerTransactionIdstring | The Directory Server transaction ID. |
initiateIntegratedSuccessAuthenticationResult.cardholderAuthenticationVerificationValuestring | The CAVV value. |
initiateIntegratedSuccessAuthenticationResult.acsUrlstring or null | The ACS URL (null if no challenge). |
initiateIntegratedSuccessAuthenticationResult.challengeDatastring or null | Challenge data (null if no challenge). |
initiateIntegratedSuccessAuthenticationResult.stateDataobject | State information. |
initiateIntegratedSuccessAuthenticationResult.cardholderInfostring | Additional cardholder information. |
transactionInitiationDataobject | Transaction initiation details. |
transactionInitiationData.psd2Dataobject | PSD2 related data. |
transactionInitiationData.threeDSecureDataobject | 3DS authentication data. |
transactionInitiationData.threeDSecureData.threeDSecureVersionstring | The 3DS version. |
transactionInitiationData.threeDSecureData.electronicCommerceIndicatorstring | The ECI value. |
transactionInitiationData.threeDSecureData.cardHolderAuthenticationVerificationValuestring | The CAVV value. |
transactionInitiationData.threeDSecureData.directoryServerTransactionIdstring | The DS transaction ID. |
transactionInitiationData.threeDSecureData.threeDSecureTransactionStatusstring | The transaction status. |
transactionInitiationData.identityVerificationobject | Identity verification data. |
transactionInitiationData.addressVerificationobject | Address verification data. |
cardTokenDataobject or null | Card token data (null for new cards). |
The onPostAuthorisation callback receives the transaction identifiers and the original payment data from Google Pay. Use these identifiers to call the Unity Backend and retrieve the full transaction outcome.
Important: The onPostAuthorisation callback does not return the transaction outcome (Authorised, Captured, Refused, etc.). You must call the Unity Backend to obtain the full transaction result. Use the Get transaction details API with the systemTransactionId to retrieve the complete transaction outcome.
| Parameter | Description |
|---|---|
resultMerchantSubmitResult or FailedSubmitResult | Object containing the transaction identifiers. Can be either a success result (MerchantSubmitResult) or failure result (FailedSubmitResult). |
result.merchantTransactionIdstring | (Success only) Your unique identifier for the transaction. Use this with systemTransactionId to retrieve full authorisation details from Unity backend. |
result.systemTransactionIdstring | (Success only) The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend. |
result.errorCodestring | (Failure only) The error code indicating the type of failure. |
result.errorReasonstring | (Failure only) The human-readable error message. |
result.correlationIdstring | (Failure only) The correlation ID for tracking and debugging. |
result.httpStatusCodenumber | (Failure only) The HTTP status code of the failure. |
paymentDataobject | The original payment data from Google Pay, including payment method details and billing information. |
onPostAuthorisation: (result, paymentData) => {
console.log('3DS Google Pay payment result');
if (result && 'merchantTransactionId' in result) {
// Success - MerchantSubmitResult
console.log('✅ Payment successful with 3DS!');
console.log('Merchant Transaction ID:', result.merchantTransactionId);
console.log('System Transaction ID:', result.systemTransactionId);
// Store transaction details
storeTransactionRecord({
merchantTransactionId: result.merchantTransactionId,
systemTransactionId: result.systemTransactionId,
paymentMethod: 'google-pay',
processingType: '3ds',
liabilityShift: true,
timestamp: new Date().toISOString()
});
// Redirect to success page
window.location.href = `/payment-success?txn=${result.merchantTransactionId}`;
} else if (result && 'errorCode' in result) {
// Failure - FailedSubmitResult
console.error('❌ Payment failed');
console.error('Error code:', result.errorCode);
console.error('Error reason:', result.errorReason);
console.error('Correlation ID:', result.correlationId);
// Handle specific error types
handlePaymentFailure(result);
}
}When the payment is successful, you'll receive a MerchantSubmitResult:
{
merchantTransactionId: "order-123456",
systemTransactionId: "sys-txn-789012"
}| Parameter | Description |
|---|---|
merchantTransactionIdstring | Your unique identifier for the transaction that was provided during SDK initialisation. |
systemTransactionIdstring | The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend. |
When the payment fails, you'll receive a FailedSubmitResult:
{
errorCode: "AUTHENTICATION_FAILED",
errorReason: "3DS authentication failed",
correlationId: "corr_abc123def456",
httpStatusCode: 402
}| Parameter | Description |
|---|---|
errorCodestring | The error code indicating the type of failure. |
errorReasonstring | A human-readable description of the error. |
correlationIdstring | A unique identifier for this transaction attempt. Use this for support inquiries and debugging. |
httpStatusCodenumber | The HTTP status code associated with the failure. |
The onError callback is triggered when an error occurs during the Google Pay flow (including 3DS authentication failures).
| Parameter | Description |
|---|---|
errorobject | Object containing error information. |
error.codestring | The error code indicating the type of error. |
error.messagestring | A human-readable description of the error. |
error.statusCodestring or undefined | Status code, if available. |
onError: (error) => {
console.error('3DS Google Pay error:', error);
// Handle specific error types
switch (error.code) {
case 'AUTHENTICATION_FAILED':
showError('Card verification failed. Please try again.');
trackError('3ds-authentication-failed', error);
break;
case 'AUTHENTICATION_TIMEOUT':
showError('Verification timed out. Please try again.');
trackError('3ds-authentication-timeout', error);
break;
case 'AUTHENTICATION_REJECTED':
showError('Payment was rejected during verification.');
trackError('3ds-authentication-rejected', error);
break;
case 'BUYER_ACCOUNT_ERROR':
showError('There was an issue with your Google Pay account. Please try another payment method.');
trackError('google-pay-account-error', error);
break;
case 'MERCHANT_CONFIGURATION_ERROR':
showError('Payment system configuration error. Please contact support.');
logError('Google Pay merchant configuration error', error);
notifySupport(error);
break;
case 'NETWORK_ERROR':
showError('Connection failed. Please check your internet and try again.');
enableRetry();
break;
case 'DEVELOPER_ERROR':
console.error('Google Pay implementation error:', error.message);
logError('Google Pay developer error', error);
showError('Payment system error. Please try again later.');
break;
default:
showError('Payment failed. Please try again or use a different payment method.');
logError('Unknown 3DS Google Pay error', error);
}
// Re-enable payment form
hideLoadingSpinner();
enablePaymentOptions();
}The onCancel callback is triggered when the customer closes the Google Pay payment sheet without completing the payment.
This callback does not receive any parameters.
onCancel: () => {
console.log('3DS Google Pay payment cancelled by user');
// Track cancellation for analytics
trackEvent('payment-cancelled', {
paymentMethod: 'google-pay',
authenticationType: '3ds',
timestamp: new Date().toISOString()
});
// Show message to user
showMessage('Payment cancelled. Your order is still in your basket.');
// Reset UI state
hideLoadingSpinner();
enablePaymentOptions();
}