Skip to content

Non-3DS payments

Process Google Pay transactions without 3D Secure authentication for faster, streamlined checkout.

Overview

By implementing a non-3DS Google Pay payment flow, you benefit from:

  • Faster checkout: Streamlined payment process with fewer steps and no authentication redirects.
  • Better conversion: Reduced friction leads to higher completion rates.
  • Simplified integration: Fewer callbacks and less complex implementation.
  • Lower latency: Direct payment processing without authentication steps.
  • Native security: Benefits from Google Pay's tokenisation and biometric authentication.

However, non-3DS payments may not qualify for liability shift protection and are best suited for low-risk transactions, trusted customers, or regions where Strong Customer Authentication (SCA) isn't mandated.

Google Pay provides inherent security through device-based authentication (fingerprint, face recognition, or PIN) and payment tokenisation, making non-3DS flows suitable for many transaction types.

Payment flow

The non-3DS Google Pay payment flow consists of five streamlined 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: Evaluation

The SDK evaluates whether 3DS authentication is required. In non-3DS flows, authentication is skipped based on:

  • Configuration not requiring 3DS (no onPreInitiateAuthentication callback provided).
  • Transaction falling below risk thresholds.
  • Regulatory exemptions being applicable.
  • Merchant risk assessment.

Since 3DS isn't required, the flow proceeds directly to authorisation.

Step 5: Authorisation

The SDK sends the authorisation request with the Google Pay token directly to the payment gateway without 3DS authentication data.

Associated callbacks

  • onPreAuthorisation: Provides transaction data for final review before authorisation. This is your last opportunity to add additional data or cancel the transaction.
  • onPostAuthorisation: Receives the final transaction result. The transaction is either authorised or declined.

Implementation

Before you start

To use non-3DS Google Pay payments:

  1. Ensure your merchant account supports non-3DS transactions.
  2. Configure Google Pay in Unity Portal with your gateway merchant ID.
  3. Whitelist your domain in Unity Portal.
  4. Consider implementing additional fraud prevention measures.

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: 25.00,
    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 non-3DS payments.

const googlePayButton = pxpSdk.create('google-pay-button', {
  paymentDataRequest: {
    allowedPaymentMethods: [{
      type: 'CARD',
      parameters: {
        allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
        allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'] // Both methods supported
      }
      // Note: tokenizationSpecification is automatically configured by the SDK from session data
    }],
    transactionInfo: {
      currencyCode: 'GBP',
      totalPriceStatus: 'FINAL',
      totalPrice: '49.99',
      displayItems: [
        {
          label: 'Subtotal',
          type: 'LINE_ITEM',
          price: '45.00'
        },
        {
          label: 'VAT',
          type: 'TAX',
          price: '4.99'
        }
      ]
    }
  },
  
  // REQUIRED: Final transaction review before authorisation
  onPreAuthorisation: async (data) => {
    console.log('Processing non-3DS Google Pay payment');
    console.log('Gateway Token ID:', data.gatewayTokenId);
    
    // Add any additional data
    return {
      riskScreeningData: {
        performRiskScreening: true,
        excludeDeviceData: false
      }
    };
  },
  
  // REQUIRED: Handle the final result
  onPostAuthorisation: (result, paymentData) => {
    console.log('Payment result:', result);
    
    if (result && 'merchantTransactionId' in result) {
      // Success - MerchantSubmitResult
      console.log('Payment successful!');
      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

Amount-based risk assessment

Apply different risk measures based on transaction amounts.

const googlePayButton = pxpSdk.create('google-pay-button', {
  // ... basic configuration
  
  onPreAuthorisation: async (data) => {
    console.log('Gateway Token ID:', data.gatewayTokenId);
    
    // Get transaction amount from your application state
    const amount = getTransactionAmount();
    
    // Different handling based on amount
    if (amount < 30) {
      // Low-value transaction - minimal checks
      return {
        riskScreeningData: {
          performRiskScreening: false,
          excludeDeviceData: true
        }
      };
    } else if (amount < 100) {
      // Medium-value transaction - standard checks
      return {
        riskScreeningData: {
          performRiskScreening: true,
          excludeDeviceData: false
        }
      };
    } else {
      // High-value transaction - enhanced checks
      return {
        riskScreeningData: {
          performRiskScreening: true,
          excludeDeviceData: false,
          items: [{
            price: amount,
            quantity: 1,
            category: 'high-value'
          }]
        }
      };
    }
  }
});

Customer type handling

Handle different customer types with varying risk profiles.

function getCustomerRiskProfile(customerId) {
  const customer = getCustomerDetails(customerId);
  
  if (!customer.previousOrders) {
    return { level: 'high', verification: 'enhanced', reason: 'new-customer' };
  }
  
  if (customer.previousOrders > 10 && customer.chargebacks === 0) {
    return { level: 'low', verification: 'minimal', reason: 'trusted-customer' };
  }
  
  return { level: 'medium', verification: 'standard', reason: 'returning-customer' };
}

const googlePayButton = pxpSdk.create('google-pay-button', {
  // ... basic configuration
  
  onPreAuthorisation: async (data) => {
    const customerId = getCurrentCustomerId();
    const riskProfile = getCustomerRiskProfile(customerId);
    
    console.log('Customer risk profile:', riskProfile);
    
    return {
      riskScreeningData: {
        performRiskScreening: riskProfile.level !== 'low',
        excludeDeviceData: false,
        transaction: {
          subtotal: amount
        }
      }
    };
  }
});

Geographic risk assessment

Apply different rules based on customer location.

const googlePayButton = pxpSdk.create('google-pay-button', {
  // ... basic configuration
  
  onPreAuthorisation: async (data) => {
    console.log('Gateway Token ID:', data.gatewayTokenId);
    
    const ipAddress = await getCustomerIPAddress();
    const geolocation = await getIPGeolocation(ipAddress);
    
    // Check if customer is in high-risk region
    const highRiskCountries = ['XX', 'YY', 'ZZ'];
    const isHighRisk = highRiskCountries.includes(geolocation.countryCode);
    
    return {
      riskScreeningData: {
        performRiskScreening: true,
        excludeDeviceData: false
      }
    };
  }
});

Step 4: Handle errors

Implement comprehensive error handling for non-3DS payments.

const googlePayButton = pxpSdk.create('google-pay-button', {
  // ... basic configuration
  
  onPostAuthorisation: (result, paymentData) => {
    if (result && 'merchantTransactionId' in result) {
      // Payment successful - MerchantSubmitResult
      handlePaymentSuccess(result, paymentData);
    } else if (result && 'errorCode' in result) {
      // Payment declined or failed - FailedSubmitResult
      handlePaymentFailure(result);
    }
  },
  
  onError: (error) => {
    console.error('Payment error:', error);
    
    // Handle specific error types
    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.');
    }
  },
  
  onCancel: () => {
    console.log('Payment cancelled by user');
    // Customer closed Google Pay sheet
    showMessage('Payment cancelled. Your order is still in your basket.');
  }
});

function handlePaymentSuccess(result, paymentData) {
  console.log('✅ Payment successful');
  console.log('Transaction ID:', result.merchantTransactionId);
  console.log('System Transaction ID:', result.systemTransactionId);
  
  // Store transaction details
  storeTransactionRecord({
    transactionId: result.merchantTransactionId,
    systemTransactionId: result.systemTransactionId,
    processingType: 'non-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();
}

Step 5: Mount the button

Mount the Google Pay button to your page.

googlePayButton.mount('google-pay-container');

Example

The following example shows a complete non-3DS Google Pay implementation.

import { useEffect, useState } from 'react';

function GooglePayCheckout() {
  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: 25.00,
        merchantTransactionId: crypto.randomUUID(),
        merchantTransactionDate: () => new Date().toISOString(),
        entryType: 'Ecom',
        intent: {
          card: 'Authorisation'
        }
      }
    });
    
    // Create Google Pay button (non-3DS)
    const button = pxpSdk.create('google-pay-button', {
      paymentDataRequest: {
        allowedPaymentMethods: [{
          type: 'CARD',
          parameters: {
            allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],
            allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
          }
          // Note: tokenizationSpecification is automatically configured by the SDK from session data
        }],
        transactionInfo: {
          currencyCode: 'GBP',
          countryCode: 'GB',
          totalPriceStatus: 'FINAL',
          totalPrice: '49.99',
          totalPriceLabel: 'Total',
          displayItems: [
            {
              label: 'Premium Headphones',
              type: 'LINE_ITEM',
              price: '45.00',
              status: 'FINAL'
            },
            {
              label: 'VAT (20%)',
              type: 'TAX',
              price: '4.99',
              status: 'FINAL'
            }
          ]
        }
      },
      
      // Handle button click
      onGooglePaymentButtonClicked: () => {
        console.log('Google Pay button clicked');
        setIsProcessing(true);
        setError(null);
      },
      
      // Pre-authorisation - add risk data
      onPreAuthorisation: async (data) => {
        console.log('🔐 Processing non-3DS payment');
        console.log('Gateway Token ID:', data.gatewayTokenId);
        
        // Assess risk
        const customerId = getCurrentCustomerId();
        const riskProfile = await assessCustomerRisk(customerId);
        
        return {
          riskScreeningData: {
            performRiskScreening: true,
            excludeDeviceData: false
          }
        };
      },
      
      // Post-authorisation - handle result
      onPostAuthorisation: (result, paymentData) => {
        console.log('📊 Payment result received');
        setIsProcessing(false);
        
        if (result && 'merchantTransactionId' in result) {
          // Success - MerchantSubmitResult
          console.log('✅ Payment authorised');
          
          // Track successful payment
          trackPaymentSuccess({
            transactionId: result.merchantTransactionId,
            systemTransactionId: result.systemTransactionId,
            paymentMethod: 'google-pay'
          });
          
          // Redirect to success page
          window.location.href = '/order-confirmation?txn=' + result.merchantTransactionId;
        } else if (result && 'errorCode' in result) {
          // Failure - FailedSubmitResult
          console.error('❌ Payment declined:', result.errorReason);
          setError('Payment declined. Please try another payment method.');
          
          // Track decline
          trackPaymentDecline({
            errorCode: result.errorCode,
            errorReason: result.errorReason,
            correlationId: result.correlationId
          });
        }
      },
      
      // Error handling
      onError: (error) => {
        console.error('❌ Payment error:', error);
        setIsProcessing(false);
        setError('An error occurred. Please try again.');
        
        // Track error
        trackPaymentError({
          errorCode: error.code,
          errorMessage: error.message
        });
      },
      
      // Cancellation handling
      onCancel: () => {
        console.log('Payment cancelled by user');
        setIsProcessing(false);
        setError(null);
        
        // Track cancellation
        trackPaymentCancellation();
      }
    });
    
    // 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 Headphones</span>
            <span45.00</span>
          </div>
          <div className="order-item">
            <span>VAT (20%)</span>
            <span4.99</span>
          </div>
          <div className="order-total">
            <span>Total</span>
            <span49.99</span>
          </div>
        </div>
      </div>
      
      <div className="payment-section">
        <h2>Pay with Google Pay</h2>
        
        {error && (
          <div className="error-message">
            {error}
          </div>
        )}
        
        <div id="google-pay-button-container"></div>
        
        {isProcessing && (
          <div className="processing-indicator">
            Processing payment...
          </div>
        )}
        
        <div className="payment-info">
          <p>✓ Fast and secure checkout with Google Pay</p>
          <p>✓ No need to enter card details</p>
          <p>✓ Protected by Google Pay's security</p>
        </div>
      </div>
    </div>
  );
}

// Helper functions
function getCurrentCustomerId() {
  return localStorage.getItem('customerId') || 'guest';
}

async function assessCustomerRisk(customerId) {
  // Implement your risk assessment logic
  const orderHistory = await getCustomerOrderHistory(customerId);
  
  if (orderHistory.totalOrders > 10 && orderHistory.chargebacks === 0) {
    return 'low';
  } else if (orderHistory.totalOrders === 0) {
    return 'high';
  }
  
  return 'medium';
}

function trackPaymentSuccess(data) {
  console.log('Tracking payment success:', data);
  // Implement your analytics tracking
}

function trackPaymentDecline(data) {
  console.log('Tracking payment decline:', data);
  // Implement your analytics tracking
}

function trackPaymentError(data) {
  console.log('Tracking payment error:', data);
  // Implement your analytics tracking
}

function trackPaymentCancellation() {
  console.log('Tracking payment cancellation');
  // Implement your analytics tracking
}

export default GooglePayCheckout;

Callback data

This section describes the data received by the different callbacks as part of the non-3DS Google Pay flow.

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('Non-3DS Google Pay authorisation for token:', data.gatewayTokenId);
  
  // Retrieve token details and make authorisation decision
  const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
  
  if (!transactionDecision) {
    // Not proceeding with authorisation
    console.log('Not proceeding with authorization');
    return null;
  }
  
  // Add risk screening data
  return {
    riskScreeningData: {
      performRiskScreening: true,
      excludeDeviceData: false
    }
  };
}

Important: 3DS external data should no longer be provided via the callback. For non-3DS flows, no 3DS data is applicable.

onPostAuthorisation

The onPostAuthorisation callback receives the authorisation result and the original payment data from Google Pay.

Event data

ParameterDescription
result
MerchantSubmitResult or FailedSubmitResult
Object containing the transaction result. Can be either a success result (MerchantSubmitResult) or failure result (FailedSubmitResult).
result.merchantTransactionId
string
(Success only) The merchant's unique identifier for the transaction.
result.systemTransactionId
string
(Success only) The system's unique identifier for the transaction.
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('Non-3DS Google Pay payment result');
  
  if (result && 'merchantTransactionId' in result) {
    // Success - MerchantSubmitResult
    console.log('✅ Payment successful!');
    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: 'non-3ds',
      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
The merchant's 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: "CARD_DECLINED",
  errorReason: "Payment declined by issuing bank",
  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 (before reaching authorisation).

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
Google Pay API status code, if available.

Example implementation

onError: (error) => {
  console.error('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('Google Pay payment cancelled by user');
  
  // Track cancellation for analytics
  trackEvent('payment-cancelled', {
    paymentMethod: 'google-pay',
    timestamp: new Date().toISOString()
  });
  
  // Show message to user
  showMessage('Payment cancelled. Your order is still in your basket.');
  
  // Reset UI state
  hideLoadingSpinner();
  enablePaymentOptions();
}