Skip to content

3DS payments

Learn about 3D Secure authentication with Google Pay for enhanced transaction security.

Overview

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.

Payment flow

The 3DS Google Pay payment flow consists of nine key steps.

Step 1: Button interaction

The customer clicks the Google Pay button, triggering the payment sheet to open. Google Pay handles device authentication (biometric or PIN) internally.

Step 2: Payment method selection

Within the Google Pay payment sheet, the customer selects their preferred payment method. Google Pay tokenises the payment data using its secure tokenisation system.

Step 3: Payment sheet completion

The customer confirms the payment in the Google Pay sheet. The SDK receives the encrypted payment token from Google Pay.

Step 4: Pre-initiation

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.

Step 5: Fingerprinting

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.

Step 6: Authentication

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.

Step 7: Authentication result

The SDK receives the 3DS authentication result indicating whether authentication was successful, failed, or requires additional action.

Step 8: Authorisation

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 (merchantTransactionId and systemTransactionId). 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.

Step 9: Authorisation result

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.

Implementation

Before you start

To use 3DS with Google Pay payments:

  1. Ensure 3D Secure is enabled in the Unity Portal for your merchant group.
  2. Configure Google Pay in Unity Portal with your gateway merchant ID.
  3. Whitelist your domain in Unity Portal.
  4. Get your 3DS provider ID from your payment processor.

Step 1: Initialise the SDK

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'
    }
  }
});

Step 2: Implement callbacks

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');
  }
});

Step 3: Handle common scenarios

Conditional 3DS

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;
  }
});

Challenge indicator selection

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
    };
  }
});

Soft decline retry

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
        })
      }
    };
  }
});

Step 4: Handle errors

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();
}

Example

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>
            <span299.99</span>
          </div>
          <div className="order-total">
            <span>Total</span>
            <span299.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;

Callback data

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.

onPostInitiateAuthentication

The onPostInitiateAuthentication callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.

Event data

ParameterDescription
data
object
Object containing the authentication initiation result.
data.authenticationId
string
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.

Example implementation

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);
}

onPreAuthentication

The onPreAuthentication callback receives no parameters. You should use the authentication decision evaluated after onPostInitiateAuthentication to determine whether to proceed.

Event data

This callback receives no parameters.

Example implementation

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
  };
}

Challenge window sizes

ValueDimensionsDescription
1250x400Compact window
2390x400Standard window
3500x600Medium window
4600x400Wide window
5FullscreenFull-screen window

Challenge indicators

ValueDescriptionWhen to use
01No preferenceStandard transactions
02No challenge requestedLow-risk transactions
03Challenge requested: Your preferenceWhen additional security desired
04Challenge requested: MandateHigh-value or high-risk transactions
05No challenge requested (transaction risk analysis performed)When you have performed risk analysis
06No challenge requested (Data share only)Token provisioning
07No challenge requested (SCA already performed)Subsequent transactions
08No challenge requested (Trust list exemption)Whitelist transactions
09Challenge requested (Trust list prompt)To add you to whitelist
10No challenge requested (Low value exemption)Low-value transactions under exemption thresholds
11No challenge requested (Secure corporate payment)Corporate card payments
12Challenge requested (Device binding prompt)Device binding scenarios
13Challenge requested (Issuer requested)When issuer specifically requires challenge
14Challenge requested (Your initiated transactions)MIT scenarios requiring challenge

onPostAuthentication

The onPostAuthentication callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.

Event data

ParameterDescription
data
object
Object containing authentication result.
data.authenticationId
string
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.

Example implementation

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...');
}

onPreAuthorisation

The onPreAuthorisation callback receives only the gateway token ID. Use this ID to retrieve token details and make authorisation decisions on your backend.

Event data

ParameterDescription
data
object
Object containing token information.
data.gatewayTokenId
string
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.

Example implementation

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.

Retrieved transaction data structure

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
}
ParameterDescription
initiateIntegratedSuccessAuthenticationResult
object
Details about the successful 3DS authentication.
initiateIntegratedSuccessAuthenticationResult.status
number
The HTTP status code.
initiateIntegratedSuccessAuthenticationResult.authenticationId
string
The unique identifier for this 3DS session.
initiateIntegratedSuccessAuthenticationResult.uniqueId
string
The unique identifier for this authentication attempt.
initiateIntegratedSuccessAuthenticationResult.state
string
The state of the authentication.
initiateIntegratedSuccessAuthenticationResult.transactionStatus
string
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.electronicCommerceIndicator
string
The Electronic Commerce Indicator (ECI).
initiateIntegratedSuccessAuthenticationResult.exemptionGranted
boolean
Whether an exemption was granted.
initiateIntegratedSuccessAuthenticationResult.exemptionGrantedByIssuer
string
The type of exemption granted by the issuer.
initiateIntegratedSuccessAuthenticationResult.threeDSecureVersion
string
The 3DS version used.
initiateIntegratedSuccessAuthenticationResult.directoryServerTransactionId
string
The Directory Server transaction ID.
initiateIntegratedSuccessAuthenticationResult.cardholderAuthenticationVerificationValue
string
The CAVV value.
initiateIntegratedSuccessAuthenticationResult.acsUrl
string or null
The ACS URL (null if no challenge).
initiateIntegratedSuccessAuthenticationResult.challengeData
string or null
Challenge data (null if no challenge).
initiateIntegratedSuccessAuthenticationResult.stateData
object
State information.
initiateIntegratedSuccessAuthenticationResult.cardholderInfo
string
Additional cardholder information.
transactionInitiationData
object
Transaction initiation details.
transactionInitiationData.psd2Data
object
PSD2 related data.
transactionInitiationData.threeDSecureData
object
3DS authentication data.
transactionInitiationData.threeDSecureData.threeDSecureVersion
string
The 3DS version.
transactionInitiationData.threeDSecureData.electronicCommerceIndicator
string
The ECI value.
transactionInitiationData.threeDSecureData.cardHolderAuthenticationVerificationValue
string
The CAVV value.
transactionInitiationData.threeDSecureData.directoryServerTransactionId
string
The DS transaction ID.
transactionInitiationData.threeDSecureData.threeDSecureTransactionStatus
string
The transaction status.
transactionInitiationData.identityVerification
object
Identity verification data.
transactionInitiationData.addressVerification
object
Address verification data.
cardTokenData
object or null
Card token data (null for new cards).

onPostAuthorisation

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.

Event data

ParameterDescription
result
MerchantSubmitResult or FailedSubmitResult
Object containing the transaction identifiers. Can be either a success result (MerchantSubmitResult) or failure result (FailedSubmitResult).
result.merchantTransactionId
string
(Success only) Your unique identifier for the transaction. Use this with systemTransactionId to retrieve full authorisation details from Unity backend.
result.systemTransactionId
string
(Success only) The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend.
result.errorCode
string
(Failure only) The error code indicating the type of failure.
result.errorReason
string
(Failure only) The human-readable error message.
result.correlationId
string
(Failure only) The correlation ID for tracking and debugging.
result.httpStatusCode
number
(Failure only) The HTTP status code of the failure.
paymentData
object
The original payment data from Google Pay, including payment method details and billing information.

Example implementation

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);
  }
}

Success result structure

When the payment is successful, you'll receive a MerchantSubmitResult:

{
  merchantTransactionId: "order-123456",
  systemTransactionId: "sys-txn-789012"
}
ParameterDescription
merchantTransactionId
string
Your unique identifier for the transaction that was provided during SDK initialisation.
systemTransactionId
string
The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend.

Failure result structure

When the payment fails, you'll receive a FailedSubmitResult:

{
  errorCode: "AUTHENTICATION_FAILED",
  errorReason: "3DS authentication failed",
  correlationId: "corr_abc123def456",
  httpStatusCode: 402
}
ParameterDescription
errorCode
string
The error code indicating the type of failure.
errorReason
string
A human-readable description of the error.
correlationId
string
A unique identifier for this transaction attempt. Use this for support inquiries and debugging.
httpStatusCode
number
The HTTP status code associated with the failure.

onError

The onError callback is triggered when an error occurs during the Google Pay flow (including 3DS authentication failures).

Event data

ParameterDescription
error
object
Object containing error information.
error.code
string
The error code indicating the type of error.
error.message
string
A human-readable description of the error.
error.statusCode
string or undefined
Status code, if available.

Example implementation

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();
}

onCancel

The onCancel callback is triggered when the customer closes the Google Pay payment sheet without completing the payment.

Event data

This callback does not receive any parameters.

Example implementation

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();
}