# 3DS transactions

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](/apis/transaction/other/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 the Unity Portal with your gateway merchant ID.
3. Whitelist your domain in the 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.


```typescript
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.


```typescript
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 risk screening data for fraud detection
    return {
      riskScreeningData: {
        performRiskScreening: true,
        userIp: "192.168.1.100",
        account: {
          id: "user_12345678",
          creationDateTime: "2024-01-15T10:30:00.000Z"
        },
        items: [{
          price: 99.99,
          quantity: 1,
          category: "Electronics",
          sku: "GPAY-PROD-001"
        }],
        fulfillments: [{
          type: "Shipped",
          shipping: {
            shippingMethod: "Express"
          },
          recipientPerson: {
            phoneNumber: "+1234567890",
            email: "customer@example.com"
          }
        }]
      }
    };
  },
  
  // 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.


```typescript
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.


```typescript
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.


```typescript
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.


```typescript
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 authorisation 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.


```typescript
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,
            userIp: "192.168.1.100",
            account: {
              id: "user_12345678",
              creationDateTime: "2024-01-15T10:30:00.000Z"
            },
            fulfillments: [{
              type: "Shipped",
              recipientPerson: {
                phoneNumber: "+1234567890"
              }
            }]
          }
        };
      },
      
      // 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;
```

## 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

| Parameter | Description |
|  --- | --- |
| `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 the Unity backend. |


Use the `authenticationId` with the [Get 3DS pre-initiate authentication details](/apis/three-d-secure-authentication/integrated-authentication/get-preinitiate-authentication-details) API to retrieve pre-authentication results including SCA mandates, exemptions, and 3DS support.

#### Example implementation


```typescript
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


```typescript
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

| Value | Dimensions | Description |
|  --- | --- | --- |
| `1` | 250x400 | Compact window |
| `2` | 390x400 | Standard window |
| `3` | 500x600 | Medium window |
| `4` | 600x400 | Wide window |
| `5` | Fullscreen | Full-screen window |


#### Challenge indicators

| 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 |
| `10` | No challenge requested (Low value exemption) | Low-value transactions under exemption thresholds |


### onPostAuthentication

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

#### Event data

| Parameter | Description |
|  --- | --- |
| `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](/apis/three-d-secure-authentication/integrated-authentication/get-authentication-details) API to retrieve the full authentication results including transaction status, ECI values, and CAVV data.

#### Example implementation


```typescript
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

| Parameter | Description |
|  --- | --- |
| `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 the Unity backend and update transaction decision. |


Use the `gatewayTokenId` with the [Get masked card data related to gateway token](/apis/token-vault/other/get-masked-card-related-to-gateway-token) API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.

#### Example implementation


```typescript
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,
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      items: [{
        price: 99.99,
        quantity: 1,
        category: "Electronics"
      }],
      fulfillments: [{
        type: "Shipped",
        shipping: {
          shippingMethod: "Express"
        },
        recipientPerson: {
          phoneNumber: "+1234567890",
          email: "customer@example.com"
        }
      }]
    }
  };
}
```

**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.


```typescript
{
  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 |
|  --- | --- |
| `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](/apis/transaction/other/get-transaction-details) API with the `systemTransactionId` to retrieve the complete transaction outcome.

#### Event data

| Parameter | Description |
|  --- | --- |
| `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 the 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 the 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


```typescript
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`:


```typescript
{
  merchantTransactionId: "order-123456",
  systemTransactionId: "sys-txn-789012"
}
```

| Parameter | Description |
|  --- | --- |
| `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 the Unity backend. |


#### Failure result structure

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


```typescript
{
  errorCode: "AUTHENTICATION_FAILED",
  errorReason: "3DS authentication failed",
  correlationId: "corr_abc123def456",
  httpStatusCode: 402
}
```

| Parameter | Description |
|  --- | --- |
| `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

| Parameter | Description |
|  --- | --- |
| `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


```typescript
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


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