# 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 the Unity Portal with your gateway merchant ID.
3. Whitelist your domain in the Unity Portal.
4. Consider implementing additional fraud prevention measures.


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

```typescript
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 risk screening data for fraud detection
    return {
      riskScreeningData: {
        performRiskScreening: true,
        excludeDeviceData: false,
        userIp: "192.168.1.100",
        account: {
          id: "user_12345678",
          creationDateTime: "2024-01-15T10:30:00.000Z"
        },
        items: [{
          price: 45.00,
          quantity: 1,
          category: "General"
        }],
        fulfillments: [{
          type: "Shipped",
          recipientPerson: {
            phoneNumber: "+1234567890"
          }
        }]
      }
    };
  },
  
  // 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.

```typescript
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,
          userIp: "192.168.1.100",
          account: {
            id: "user_12345678",
            creationDateTime: "2024-01-15T10:30:00.000Z"
          }
        }
      };
    } else if (amount < 100) {
      // Medium-value transaction - standard checks
      return {
        riskScreeningData: {
          performRiskScreening: true,
          excludeDeviceData: false,
          userIp: "192.168.1.100",
          account: {
            id: "user_12345678",
            creationDateTime: "2024-01-15T10:30:00.000Z"
          },
          fulfillments: [{
            type: "Shipped",
            recipientPerson: {
              phoneNumber: "+1234567890"
            }
          }]
        }
      };
    } else {
      // High-value transaction - enhanced checks
      return {
        riskScreeningData: {
          performRiskScreening: true,
          excludeDeviceData: false,
          userIp: "192.168.1.100",
          account: {
            id: "user_12345678",
            creationDateTime: "2024-01-15T10:30:00.000Z"
          },
          items: [{
            price: amount,
            quantity: 1,
            category: 'high-value'
          }],
          fulfillments: [{
            type: "Shipped",
            shipping: {
              shippingMethod: "Express"
            },
            recipientPerson: {
              phoneNumber: "+1234567890",
              email: "customer@example.com"
            }
          }]
        }
      };
    }
  }
});
```

#### Customer type handling

Handle different customer types with varying risk profiles.

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

#### Geographic risk assessment

Apply different rules based on customer location.

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

### Step 4: Handle errors

Implement comprehensive error handling for non-3DS payments.

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

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

## Example

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

```typescript
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,
            userIp: "192.168.1.100",
            account: {
              id: customerId,
              creationDateTime: "2024-01-15T10:30:00.000Z"
            },
            fulfillments: [{
              type: "Shipped",
              recipientPerson: {
                phoneNumber: "+1234567890"
              }
            }]
          }
        };
      },
      
      // 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>
            <span>£45.00</span>
          </div>
          <div className="order-item">
            <span>VAT (20%)</span>
            <span>£4.99</span>
          </div>
          <div className="order-total">
            <span>Total</span>
            <span>£49.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

| 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('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 authorisation');
    return null;
  }
  
  // Add risk screening data
  return {
    riskScreeningData: {
      performRiskScreening: true,
      excludeDeviceData: false,
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      items: [{
        price: 45.00,
        quantity: 1,
        category: "General"
      }],
      fulfillments: [{
        type: "Shipped",
        shipping: {
          shippingMethod: "Standard"
        },
        recipientPerson: {
          phoneNumber: "+1234567890",
          email: "customer@example.com"
        }
      }]
    }
  };
}
```

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

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

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

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

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


#### Failure result structure

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

```typescript
{
  errorCode: "CARD_DECLINED",
  errorReason: "Payment declined by issuing bank",
  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 (before reaching authorisation).

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


#### Example implementation

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

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