# Troubleshooting

Learn how to diagnose and fix common issues with the Paze web component.

## Exception types and error codes

### Component exceptions

The Paze web component throws specific exceptions for different error scenarios:

| Exception  | Error code | Description |
|  --- | --- | --- |
| `PazeLoadFailedException` | `SDK1200` | The Paze SDK failed to load. For example, due to network issues, blocked resources, or CSP restrictions. |
| `PazeInitializationFailedException` | `SDK1206`, `SDK1207` | Paze SDK initialisation failed, or `canCheckout()` did not return an eligibility result. |
| `PazeConfigurationValidationFailedException` | `SDK1202-1205`, `SDK1222-1232` | Configuration validation failed. For example, due to a missing client ID, invalid lengths, or incorrect billing preferences. |
| `PazeDynamicIdentityRequiredException` | `SDK1208` | Dynamic presentment requires both email and phone number to be provided. |
| `PazeIdentityValidationFailedException` | `SDK1209`-`SDK1212` | Email or phone number validation failed. Check format and length requirements. |
| `PazeCurrencyNotSupportedException` | `SDK1213` | Paze only supports `USD` currency. The provided currency isn't supported. |
| `PazePaymentFailedException` | `SDK1214` | Paze checkout or wallet flow failed before authorisation. For example, due to network issues or an error returned by the Paze SDK. |
| `PazeTransactionFailedException` | `SDK1235` | Transaction submission failed. The exception includes a `submitResult` with error details. `onSubmitError` receives the same failure data. |
| `PazeIncompleteCheckoutException` | `SDK1221` | Paze `complete()` was called before `checkout()` finished successfully. |
| `PazeCheckoutResponseMissingException` | `SDK1215` | Paze checkout did not return a `checkoutResponse` for a `COMPLETE` result. |
| `PazeCompleteResponseMissingException` | `SDK1216` | Paze `complete()` did not return a `completeResponse`. |
| `PazeCheckoutDecodeFailedException` | `SDK1217` | Unable to decode the Paze `checkoutResponse`. |
| `PazeCompleteDecodeFailedException` | `SDK1218` | Unable to decode the Paze `completeResponse`. |
| `PazeCompleteSecuredPayloadMissingException` | `SDK1219` | Paze `completeResponse` did not contain a `securedPayload` for decryption. |
| `PazeDecryptFailedException` | `SDK1233` | Token decryption failed. Check session configuration and Paze wallet settings in Unity Portal. |
| `PazeAccountInaccessibleException` | `SDK1220` | Paze wallet account exists but is not accessible to the merchant. |
| `PazeInvalidRequestException` | `SDK1234` | Invalid request parameters or configuration returned by the Paze SDK. |
| `UnsupportedFundingTypePazeSdkException` | `SDK0118` | Paze is not included in the allowed funding types for this session. |


User cancellation through `onCheckoutIncomplete` is not thrown as an exception. Use the callback to handle cancellations gracefully.

## Error handling best practices

### Comprehensive error handler

Use a single component configuration that routes `onError`, `onCheckoutIncomplete`, and `onSubmitError` to appropriate user messaging and logging:

```typescript
const config = {
  onError: (error) => {
    // Log error for debugging
    console.error('Paze error:', {
      name: error.name,
      message: error.message,
      code: error.ErrorCode, // Note: Capital 'E' in ErrorCode
      stack: error.stack,
      timestamp: new Date().toISOString()
    });

    // Handle specific error types by exception name
    if (error.name === 'PazeLoadFailedException') {
      showUserMessage('Unable to load Paze. Please check your connection and try again.');
      offerAlternativePaymentMethods();
      
    } else if (error.name === 'PazeCurrencyNotSupportedException') {
      showUserMessage('Paze is only available for USD transactions. Please use an alternative payment method.');
      showAlternativePaymentMethods();
      
    } else if (error.name === 'PazeIdentityValidationFailedException') {
      showUserMessage('Please provide a valid email address to use Paze.');
      highlightEmailField();
      
    } else if (error.name === 'PazeDynamicIdentityRequiredException') {
      showUserMessage('Please provide both email address and phone number to use Paze.');
      highlightIdentityFields();
      
    } else if (error.name === 'PazeConfigurationValidationFailedException') {
      showUserMessage('Payment system temporarily unavailable. Please try again in a few moments.');
      logCriticalError('Configuration validation error', error);
      
    } else if (error.name === 'PazePaymentFailedException') {
      showUserMessage('Payment failed. Please check your payment information and try again.');
      offerRetryOption();
      
    } else if (error.name === 'PazeDecryptFailedException') {
      showUserMessage('Payment processing error. Please try again.');
      logCriticalError('Token decryption error', error);
      
    } else if (error.name === 'PazeAccountInaccessibleException') {
      showUserMessage('Unable to access your Paze wallet. Please use an alternative payment method.');
      showAlternativePaymentMethods();
      
    } else if (error.name === 'PazeInitializationFailedException') {
      showUserMessage('Payment system not ready. Please refresh and try again.');
      logCriticalError('Paze initialization error', error);
      
    } else {
      showUserMessage('An unexpected error occurred. Please try again or contact support.');
      logUnknownError(error);
    }
  },
  
  // Handle checkout cancellation separately (not an exception)
  onCheckoutIncomplete: (result) => {
    console.log('Paze checkout cancelled or incomplete');
    trackEvent('paze_checkout_cancelled');
    showUserMessage('Payment was cancelled. You can try again when ready.');
  },
  
  // Handle submission errors (FailedSubmitResult, PazeTransactionFailedException, or BaseSdkException)
  onSubmitError: (submitError) => {
    console.error('Paze submission error:', submitError);

    const failed = 'submitResult' in submitError
      ? submitError.submitResult
      : 'errorCode' in submitError
        ? submitError
        : null;

    if (failed?.errorCode === 'DECLINED') {
      showUserMessage('Payment was declined. Please try a different payment method.');
    } else if (failed?.errorReason?.toLowerCase().includes('insufficient funds')) {
      showUserMessage('Insufficient funds. Please try a different payment method.');
    } else if ('ErrorCode' in submitError) {
      showUserMessage(submitError.message);
    } else {
      showUserMessage('Payment failed. Please try again or use a different payment method.');
    }
  }
};

function showUserMessage(message) {
  const errorDiv = document.createElement('div');
  errorDiv.className = 'error-message';
  errorDiv.textContent = message;
  document.getElementById('payment-container').appendChild(errorDiv);
  
  // Auto-dismiss after 5 seconds
  setTimeout(() => {
    errorDiv.remove();
  }, 5000);
}

function logCriticalError(context, error) {
  // Send to error monitoring service
  if (window.errorReporting) {
    window.errorReporting.captureException(error, { context });
  }
}

function offerAlternativePaymentMethods() {
  document.getElementById('paze-container').style.display = 'none';
  document.getElementById('alternative-payments').style.display = 'block';
}
```

## Portal and session setup

Use this section when Paze is not appearing in the Unity Portal, missing from your session response, or failing during component initialisation with `SDK1202`. For portal setup steps, see [Onboarding](/guides/checkout/components/web/paze/onboarding).

### Paze service not visible on site

**Symptom:** The Paze service does not appear in your site's Services tab.

**Solution:** Enable Paze at the merchant group level first ([Onboarding, Step 1](/guides/checkout/components/web/paze/onboarding#step-1-ensure-paze-is-enabled-at-the-merchant-group-level)), then return to the site configuration.

### Missing client ID in session

**Symptom:** Session response does not include `allowedFundingTypes.wallets.paze.clientId`.

**Solution:**

1. Verify the client ID is saved in **Paze Account Settings** for the correct site.
2. Create a new session after saving portal changes.
3. Confirm you are using the correct site name in your session request.


### SDK1202 error on mount

**Symptom:** Component fails with `Missing session.allowedFundingTypes.wallets.paze.clientId`.

**Solution:** The session was created before Paze was configured, or Paze is not enabled for the site. Complete [Onboarding, Steps 3–4](/guides/checkout/components/web/paze/onboarding#step-3-configure-paze-on-your-site), then re-create the session.

## Troubleshooting common issues

### Paze button isn't showing

The symptoms of this are:

- The button element is hidden or not rendered.
- There's an empty container where the button should appear.
- No Paze option is visible to customers.


#### Diagnostic steps

Run these checks in your browser console to narrow down why the button is not visible:

```typescript
// Step 1: Check Paze SDK availability
console.log('Checking Paze SDK availability...');

// Check if Paze SDK is loaded
if (!window.DIGITAL_WALLET_SDK || !window.DIGITAL_WALLET_SDK.canCheckout) {
  console.error('Paze SDK not loaded');
  console.log('Ensure the Paze SDK script is included and loaded');
  return;
}

// Step 2: Verify session configuration
const sessionPazeConfig = sessionData?.allowedFundingTypes?.wallets?.paze;
if (!sessionPazeConfig) {
  console.error('Paze not configured in session');
  console.log('Verify Paze is enabled in Unity Portal and included in session response');
  return;
}

console.log('Session Paze config:', {
  clientId: sessionPazeConfig.clientId ? 'Present' : 'Missing',
  profileId: sessionPazeConfig.profileId ? 'Present' : 'Not set'
});

// Step 3: Check browser compatibility
const browserInfo = {
  userAgent: navigator.userAgent,
  isChrome: /Chrome\/(\d+)/.test(navigator.userAgent),
  isSafari: /Safari\//.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
  isFirefox: /Firefox\/(\d+)/.test(navigator.userAgent)
};
console.log('Browser info:', browserInfo);

// Step 4: Check HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  console.error('Paze requires HTTPS in production');
  return;
}

// Step 5: Verify component configuration
const pazeConfig = {
  buttonRenderMode: config.buttonRenderMode,
  emailAddress: config.emailAddress,
  phoneNumber: config.phoneNumber
};

console.log('Paze component config:', pazeConfig);

if (config.buttonRenderMode === 'dynamic') {
  if (!config.emailAddress || !config.phoneNumber) {
    console.error('Dynamic presentment requires both emailAddress and phoneNumber');
    return;
  }
}

// Step 6: Test presentment
try {
  console.log('Testing Paze presentment...');
  // Presentment check happens automatically during component creation
  // Monitor onPresentmentResolved callback
} catch (error) {
  console.error('Paze presentment test failed:', error);
}

console.log('All diagnostic checks complete');
```

#### Solutions

Use these patterns to hide Paze gracefully when it is unavailable and to validate identity before dynamic presentment:

```typescript
// Solution 1: Graceful fallback when Paze not available
function initializePaze() {
  if (checkPazeAvailability()) {
    renderPazeButton();
  } else {
    console.log('Paze not available, showing alternatives');
    renderAlternativePaymentMethods();
  }
}

function checkPazeAvailability() {
  return window.DIGITAL_WALLET_SDK && 
         window.DIGITAL_WALLET_SDK.canCheckout &&
         sessionData?.allowedFundingTypes?.wallets?.paze?.clientId &&
         isHttps() &&
         hasValidConfiguration();
}

function isHttps() {
  return location.protocol === 'https:' || location.hostname === 'localhost';
}

function hasValidConfiguration() {
  if (!config.buttonRenderMode) return false;

  if (config.buttonRenderMode === 'dynamic') {
    return config.emailAddress &&
           validateEmail(config.emailAddress) &&
           config.phoneNumber &&
           /^1\d{10}$/.test(config.phoneNumber);
  }

  if (config.emailAddress && !validateEmail(config.emailAddress)) return false;
  if (config.phoneNumber && !/^1\d{10}$/.test(config.phoneNumber)) return false;

  return true;
}

function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email) && email.length <= 128;
}

function renderAlternativePaymentMethods() {
  const container = document.getElementById('payment-buttons');
  container.innerHTML = `
    <button class="credit-card-button">Pay with Credit Card</button>
    <button class="google-pay-button">Pay with Google Pay</button>
  `;
}

// Solution 2: Validate identity for dynamic presentment
function initializePazeWithValidation() {
  const email = document.getElementById('customer-email')?.value;
  const phone = document.getElementById('customer-phone')?.value;

  if (!email || !phone) {
    console.warn('Dynamic presentment requires both email and phone number');
    document.getElementById('paze-container').style.display = 'none';
    document.getElementById('identity-required-message').style.display = 'block';
    return;
  }

  if (!validateEmail(email)) {
    console.error('Invalid email format:', email);
    showError('Please enter a valid email address');
    return;
  }

  if (!/^1\d{10}$/.test(phone)) {
    console.error('Invalid phone format:', phone);
    showError('Please enter a valid US phone number without +, for example 15551234567');
    return;
  }

  const pazeButton = pxpSdk.create('paze-button', {
    buttonRenderMode: 'dynamic',
    emailAddress: email,
    phoneNumber: phone,
    onPresentmentResolved: (isVisible) => {
      if (!isVisible) {
        console.log('No Paze wallet found for this email');
        document.getElementById('paze-container').style.display = 'none';
      }
    },
    onError: (error) => {
      console.error('Paze error:', error);
      handlePazeError(error);
    }
  });
  
  pazeButton.mount('paze-container');
}

// Solution 3: Progressive loading with timeout
function loadPazeWithFallback() {
  const timeout = setTimeout(() => {
    console.warn('Paze SDK loading timeout');
    renderAlternativePaymentMethods();
  }, 5000); // 5 second timeout
  
  // Check if Paze SDK is already loaded
  if (window.DIGITAL_WALLET_SDK) {
    clearTimeout(timeout);
    initializePaze();
    return;
  }
  
  // Wait for Paze SDK to load
  const checkInterval = setInterval(() => {
    if (window.DIGITAL_WALLET_SDK) {
      clearInterval(checkInterval);
      clearTimeout(timeout);
      console.log('Paze SDK loaded successfully');
      initializePaze();
    }
  }, 100);
}
```

### Wallet presentment not resolving

The symptoms of this are:

- `onPresentmentResolved` callback never fires.
- Button remains in loading state indefinitely.
- No visibility determination for Paze button.


In `static` mode, `onPresentmentResolved(true)` fires after successful initialisation. In `dynamic` mode, `isVisible` is `false` when `canCheckout()` reports the shopper has no eligible Paze wallet.

#### Diagnostic steps

Add logging callbacks to trace initialisation, presentment, and validation errors:

```typescript
// Enhanced presentment diagnostics
const pazeConfig = {
  buttonRenderMode: 'static',
  emailAddress: 'customer@example.com',
  
  onInit: () => {
    console.log('✅ Paze component initialized');
  },
  
  onPresentmentResolved: (isVisible) => {
    console.group('🔍 Paze Presentment Diagnostics');
    
    console.log('Presentment resolved:', {
      isVisible,
      timestamp: new Date().toISOString()
    });
    
    if (!isVisible) {
      console.log('ℹ️ Reasons wallet might not be visible:');
      console.log('- No Paze wallet registered for this email');
      console.log('- Email not verified in Paze system');
      console.log('- Paze service temporarily unavailable');
    }
    
    console.groupEnd();
  },
  
  onError: (error) => {
    console.error('Presentment error:', error);
    
    if (error.ErrorCode === 'SDK1206' || error.ErrorCode === 'SDK1207') {
      console.error('Paze initialization failed');
      console.log('Check: clientId in session configuration');
      console.log('Check: Network connectivity to Paze servers');
    } else if (error.ErrorCode === 'SDK1209' || error.ErrorCode === 'SDK1210') {
      console.error('Email validation failed:', error.message);
      console.log('Check: Email format (RFC 5322)');
      console.log('Check: Email length (max 128 characters)');
    }
  }
};

// Add timeout for presentment
let presentmentTimeout = setTimeout(() => {
  console.warn('Presentment timeout after 10 seconds');
  console.log('Paze presentment did not resolve in expected time');
  // Hide button or show alternative
  document.getElementById('paze-container').style.display = 'none';
}, 10000);

// Clear timeout when presentment resolves
pazeConfig.onPresentmentResolved = (isVisible) => {
  clearTimeout(presentmentTimeout);
  // ... rest of handler
};
```

#### Solutions

Implement a presentment timeout and validate identity before creating the component:

```typescript
// Solution 1: Implement presentment timeout and fallback
function createPazeWithTimeout() {
  let presentmentResolved = false;
  
  const pazeButton = pxpSdk.create('paze-button', {
    buttonRenderMode: 'static',
    emailAddress: customerEmail,
    
    onPresentmentResolved: (isVisible) => {
      presentmentResolved = true;
      
      if (isVisible) {
        console.log('Paze wallet available');
        document.getElementById('paze-container').style.display = 'block';
      } else {
        console.log('No Paze wallet found');
        document.getElementById('paze-container').style.display = 'none';
        document.getElementById('paze-unavailable-message').style.display = 'block';
      }
    },
    
    onError: (error) => {
      presentmentResolved = true;
      console.error('Paze presentment error:', error);
      document.getElementById('paze-container').style.display = 'none';
    }
  });
  
  pazeButton.mount('paze-container');
  
  // Timeout fallback
  setTimeout(() => {
    if (!presentmentResolved) {
      console.warn('Paze presentment timeout - hiding button');
      document.getElementById('paze-container').style.display = 'none';
    }
  }, 10000);
}

// Solution 2: Validate email before creating component
function createPazeWithValidation() {
  const email = getCustomerEmail();
  
  // Validate email format
  if (!email || !validatePazeEmail(email)) {
    console.error('Invalid email for Paze');
    return;
  }
  
  console.log('Creating Paze button with validated email:', email);
  
  const pazeButton = pxpSdk.create('paze-button', {
    buttonRenderMode: 'static',
    emailAddress: email,
    onPresentmentResolved: handlePresentment
  });
  
  pazeButton.mount('paze-container');
}

function validatePazeEmail(email) {
  // Check format (RFC 5322 compliant)
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(email)) {
    console.error('Email format invalid');
    return false;
  }
  
  // Check length
  if (email.length > 128) {
    console.error('Email too long (max 128 characters)');
    return false;
  }
  
  return true;
}

function handlePresentment(isVisible) {
  if (isVisible) {
    // Show Paze button
    document.getElementById('paze-container').classList.add('visible');
    trackEvent('paze_wallet_available');
  } else {
    // Hide Paze, offer alternatives
    document.getElementById('paze-container').style.display = 'none';
    document.getElementById('alternative-payment-methods').style.display = 'block';
    trackEvent('paze_wallet_not_available');
  }
}
```

### Checkout fails or gets cancelled

The symptoms of this are:

- Customer clicks Paze button but checkout doesn't complete.
- `onCheckoutIncomplete` fires unexpectedly.
- Paze wallet opens but customer can't complete payment.


#### Diagnostic steps

Instrument the checkout callbacks to log each step and capture the incomplete reason:

```typescript
// Comprehensive checkout flow diagnostics
const pazeConfig = {
  buttonRenderMode: 'static',
  emailAddress: 'customer@example.com',
  
  onPazeButtonClicked: (event) => {
    console.group('🔍 Paze Checkout Flow');
    console.log('Step 1: Button clicked');
    console.log('Email:', config.emailAddress);
    console.log('Currency:', pxpSdk.getConfig().transactionData.currency);
    console.log('Amount:', pxpSdk.getConfig().transactionData.amount);
  },
  
  onCheckoutComplete: (result) => {
    console.log('Step 2: Checkout complete');
    console.log('Result:', result.result);
    console.log('Session ID:', result.checkoutDecodedResponse?.sessionId);
    console.log('Card network:', result.checkoutDecodedResponse?.maskedCard?.paymentCardNetwork);
    console.groupEnd();
    
    return true;
  },
  
  onCheckoutIncomplete: (result) => {
    console.warn('Checkout incomplete or cancelled');
    console.log('Reason:', result.reason);
    console.log('Session ID:', result.checkoutDecodedResponse?.sessionId);
    console.groupEnd();
    
    if (result.reason?.toLowerCase().includes('cancel')) {
      trackEvent('paze_user_cancelled');
    } else {
      trackEvent('paze_checkout_error', { reason: result.reason });
    }
  },
  
  onError: (error) => {
    console.error('Checkout error:', error);
    console.groupEnd();
    
    if (error.ErrorCode === 'SDK1213') {
      console.error('Currency not supported - Paze only supports USD');
    } else if (error.ErrorCode === 'SDK1214') {
      console.error('Checkout failed:', error.message);
    }
  }
};
```

#### Solutions

Validate currency before mounting the component and handle incomplete checkout with clear customer messaging:

```typescript
// Solution 1: Currency validation before checkout
function initializePazeWithCurrencyCheck() {
  const currency = pxpSdk.getConfig().transactionData.currency;
  
  if (currency !== 'USD') {
    console.warn('Paze only supports USD, current currency:', currency);
    document.getElementById('paze-container').style.display = 'none';
    document.getElementById('currency-notice').textContent = 
      `Paze is only available for USD transactions. Current currency: ${currency}`;
    return;
  }
  
  const pazeButton = pxpSdk.create('paze-button', {
    buttonRenderMode: 'static',
    emailAddress: customerEmail,
    onCheckoutComplete: handleCheckoutComplete,
    onCheckoutIncomplete: handleCheckoutIncomplete
  });
  
  pazeButton.mount('paze-container');
}

function handleCheckoutComplete(result) {
  console.log('Paze checkout successful');
  
  // Show success indicator
  showCheckoutSuccess();
  
  // Proceed with decryption
  return true;
}

function handleCheckoutIncomplete(result) {
  console.log('Checkout incomplete:', result.reason);
  
  if (result.reason?.toLowerCase().includes('cancel')) {
    showMessage('Payment cancelled. You can try again when ready.');
  } else {
    showMessage('Unable to complete checkout. Please try a different payment method.');
    showAlternativePaymentMethods();
  }
}

// Solution 2: Implement checkout timeout handling
function createPazeWithCheckoutTimeout() {
  let checkoutTimeout;
  
  const pazeButton = pxpSdk.create('paze-button', {
    buttonRenderMode: 'static',
    emailAddress: customerEmail,
    
    onPazeButtonClicked: () => {
      console.log('Starting checkout, setting 5 minute timeout');
      
      // Set 5-minute timeout for checkout
      checkoutTimeout = setTimeout(() => {
        console.warn('Checkout timeout - 5 minutes elapsed');
        showMessage('Checkout timed out. Please try again.');
        hideLoadingIndicator();
      }, 5 * 60 * 1000);
      
      showLoadingIndicator('Opening Paze wallet...');
    },
    
    onCheckoutComplete: (result) => {
      clearTimeout(checkoutTimeout);
      console.log('Checkout completed successfully');
      return true;
    },
    
    onCheckoutIncomplete: (result) => {
      clearTimeout(checkoutTimeout);
      console.log('Checkout incomplete');
      hideLoadingIndicator();
    },
    
    onError: (error) => {
      clearTimeout(checkoutTimeout);
      console.error('Checkout error:', error);
      hideLoadingIndicator();
    }
  });
  
  pazeButton.mount('paze-container');
}
```

### Token decryption fails

The symptoms of this are:

- Checkout completes but payment doesn't process.
- `onPostDecryption` never fires.
- Error: "Token decryption failed" (`SDK1233`).


#### Diagnostic steps

Log each stage of the decryption flow to see whether SDK-managed or manual decryption is failing:

```typescript
// Decryption flow diagnostics
const pazeConfig = {
  buttonRenderMode: 'static',
  emailAddress: 'customer@example.com',
  
  onCheckoutComplete: (result) => {
    console.group('🔐 Decryption Flow Diagnostics');
    console.log('Step 1: Checkout complete, preparing for decryption');
    return true;
  },
  
  onPreDecryption: () => {
    console.log('Step 2: Pre-decryption');
    console.log('Decryption method: SDK-managed (automatic)');
    
    // Check session encryption key
    const session = pxpSdk.getConfig().session;
    console.log('Session encryption key present:', !!session.encryptionKey);
    
    return true;
  },
  
  onPostDecryption: (decryptedPayload) => {
    console.log('Step 3: Post-decryption');
    console.log('Decryption successful');
    console.log('Network token present:', !!decryptedPayload.fundingData?.networkToken);
    console.groupEnd();
  },
  
  onError: (error) => {
    console.error('Decryption error:', error);
    console.groupEnd();
    
    if (error.ErrorCode === 'SDK1233') {
      console.error('Token decryption failed');
      console.log('Check: Session encryption key');
      console.log('Check: Gateway configuration in Unity Portal');
      console.log('Check: Network connectivity');
    } else if (error.ErrorCode === 'SDK1219') {
      console.error('Secured payload missing from complete response');
    }
  }
};

// Manual decryption diagnostics
const manualDecryptionConfig = {
  onPreDecryption: () => {
    console.log('Manual decryption mode enabled');
    return false;
  },
  
  onComplete: async (result) => {
    console.group('Manual Decryption Diagnostics');
    const securedPayload = result.completeDecodedResponse?.securedPayload;
    console.log('Secured payload received');
    console.log('Has secured payload:', !!securedPayload);
    console.log('Payload ID:', result.completeDecodedResponse?.payloadId);
    
    try {
      // Forward securedPayload to your backend (never call PXP decrypt API from the browser)
      console.log('Calling merchant decrypt endpoint...');
      const response = await fetch('/api/paze/decrypt', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ securedPayload })
      });
      
      console.log('Decryption API response status:', response.status);
      
      if (!response.ok) {
        console.error('Decryption API failed:', await response.text());
      } else {
        console.log('Manual decryption successful');
      }
      
    } catch (error) {
      console.error('Manual decryption error:', error);
    }
    
    console.groupEnd();
  }
};
```

#### Solutions

Verify the session before decryption, recover from session expiry, and handle manual decryption with retries. For the PXP decrypt-token API, see [Backend decryption](/guides/checkout/components/web/paze/implementation#backend-decryption).

```typescript
// Solution 1: Verify session encryption key
function verifySessionForPaze() {
  const session = pxpSdk.getConfig().session;
  
  if (!session.encryptionKey) {
    console.error('Session encryption key missing');
    showError('Payment system configuration error. Please refresh and try again.');
    return false;
  }
  
  if (!session.sessionId) {
    console.error('Session ID missing');
    showError('Session invalid. Please refresh and try again.');
    return false;
  }
  
  // Check session expiry when your backend includes sessionExpiry in the session response
  if (session.sessionExpiry) {
    const expiresAt = new Date(session.sessionExpiry);
    const now = new Date();

    if (expiresAt < now) {
      console.error('Session expired');
      showError('Session expired. Please refresh and try again.');
      setTimeout(() => window.location.reload(), 2000);
      return false;
    }
  }
  
  console.log('Session validated for Paze decryption');
  return true;
}

// Solution 2: Implement decryption error recovery
const pazeConfig = {
  onPreDecryption: () => {
    // Verify session before decryption
    if (!verifySessionForPaze()) {
      return false;
    }
    
    return true;
  },
  
  onError: async (error) => {
    if (error.ErrorCode === 'SDK1233') {
      console.error('Decryption failed, attempting recovery');
      
      // Check if session needs refresh
      const session = pxpSdk.getConfig().session;
      if (session.sessionExpiry) {
        const expiresAt = new Date(session.sessionExpiry);
        const now = new Date();
        const minutesUntilExpiry = (expiresAt - now) / (1000 * 60);

        if (minutesUntilExpiry < 5) {
          showMessage('Session expired. Refreshing...');
          
          try {
            const newSession = await fetch('/api/sessions').then(r => r.json());

            // Reinitialise the SDK with the refreshed session
            pxpSdk = PxpCheckout.initialize({
              ...existingSdkConfig,
              session: newSession
            });
            
            showMessage('Please try your payment again.');
            return;
          } catch (refreshError) {
            showError('Unable to refresh session. Please reload the page.');
            return;
          }
        }
      }

      showError('Decryption failed. Please try again or use a different payment method.');
    }
  }
};

// Solution 3: Manual decryption with error handling
const manualDecryptionConfig = {
  onPreDecryption: () => {
    return false;
  },
  
  onComplete: async (result) => {
    try {
      showLoadingIndicator('Processing payment...');
      
      const securedPayload = result.completeDecodedResponse?.securedPayload;
      
      // Validate payload
      if (!securedPayload) {
        throw new Error('Secured payload missing');
      }
      
      // Call your backend decrypt endpoint with retry
      const decryptedData = await retryDecryption({ securedPayload }, 3);
      
      console.log('Manual decryption successful');
      
      // Submit payment with decrypted data
      const paymentResult = await submitPayment(decryptedData);
      
      if (paymentResult.success) {
        window.location.href = '/success';
      } else {
        showError('Payment failed: ' + paymentResult.error);
      }
      
    } catch (error) {
      console.error('Manual decryption failed:', error);
      showError('Unable to process payment. Please try again or use a different payment method.');
    } finally {
      hideLoadingIndicator();
    }
  }
};

async function retryDecryption(result, maxAttempts) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      console.log(`Decryption attempt ${attempt}/${maxAttempts}`);
      
      const response = await fetch('/api/paze/decrypt', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-HMAC-Signature': await generateHmacSignature(result.securedPayload)
        },
        body: JSON.stringify({
          securedPayload: result.securedPayload
        })
      });
      
      if (!response.ok) {
        throw new Error(`Decryption API returned ${response.status}`);
      }
      
      return await response.json();
      
    } catch (error) {
      console.warn(`Attempt ${attempt} failed:`, error.message);
      
      if (attempt < maxAttempts) {
        // Exponential backoff
        await delay(Math.min(1000 * Math.pow(2, attempt), 5000));
      } else {
        throw error;
      }
    }
  }
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

### Payment authorisation failures

The symptoms of this are:

- Decryption succeeds but authorisation fails.
- `onPostAuthorisation` receives a `state` other than `Authorised` (when configured).
- `onSubmitError` fires for HTTP or validation failures.
- Transaction declined or rejected.


Configure `onPostAuthorisation` on the component to receive authorisation outcomes. HTTP failures route to `onSubmitError` instead.

#### Diagnostic steps

Log transaction data before submit and inspect the authorisation response state:

```typescript
// Authorisation flow diagnostics
const pazeConfig = {
  onPreAuthorisation: async () => {
    console.group('💳 Payment authorisation diagnostics');
    console.log('Step 1: Pre-authorisation');
    
    // Check transaction data
    const transactionData = pxpSdk.getConfig().transactionData;
    console.log('Transaction data:', {
      amount: transactionData.amount,
      currency: transactionData.currency,
      merchantTransactionId: transactionData.merchantTransactionId
    });
    
    // Validate amount
    if (transactionData.amount <= 0) {
      console.error('Invalid amount:', transactionData.amount);
      return false; // Cancel authorisation
    }
    
    console.log('Pre-authorisation checks passed');
    
    return {
      riskScreeningData: {
        performRiskScreening: true,
        userIp: "192.168.1.100"
      }
    };
  },
  
  onPostAuthorisation: (data) => {
    console.log('Step 2: Post-authorisation');
    
    const isSuccess = data.state === 'Authorised';
    console.log('Result type:', isSuccess ? 'Success' : 'Failure');
    
    if (isSuccess) {
      console.log('Authorisation successful');
      console.log('Merchant transaction ID:', data.merchantTransactionId);
      console.log('System transaction ID:', data.systemTransactionId);
    } else {
      console.error('Authorisation failed');
      console.error('State:', data.state);
      console.error('Provider response code:', data.providerResponseCode);
      console.error('State data code:', data.stateDataCode);
      
      if (data.state === 'Declined') {
        console.error('Payment declined by issuer');
      }
    }
    
    console.groupEnd();
  },
  
  onSubmitError: (submitError) => {
    const failed = 'submitResult' in submitError
      ? submitError.submitResult
      : 'errorCode' in submitError
        ? submitError
        : null;

    console.error('Submit error:', failed ?? submitError);
  }
};
```

#### Solutions

Verify successful authorisation on your backend before fulfilling the order and track declined states separately from submission errors:

```typescript
// Solution 1: Comprehensive authorization handling
const pazeConfig = {
  onPostAuthorisation: async (data) => {
    if (data.state === 'Authorised') {
      console.log('Payment authorized successfully');
      
      // Verify on backend before fulfilling
      const verified = await verifyPaymentOnBackend({
        systemTransactionId: data.systemTransactionId,
        merchantTransactionId: data.merchantTransactionId
      });
      
      if (verified.success) {
        showSuccessMessage('Payment successful!');
        setTimeout(() => {
          window.location.href = `/success?orderId=${verified.orderId}`;
        }, 2000);
      } else {
        showError('Payment verification failed. Please contact support.');
      }
      
    } else {
      showError(`Payment failed: ${data.state || 'Unknown error'}`);
      trackEvent('paze_authorization_failed', {
        state: data.state,
        providerResponseCode: data.providerResponseCode
      });
      
      // Show alternative payment methods
      showAlternativePaymentMethods();
    }
  },
  
  onSubmitError: (submitError) => {
    console.error('Payment submission error:', submitError);

    const message = 'submitResult' in submitError
      ? submitError.submitResult.errorReason
      : 'errorCode' in submitError
        ? submitError.errorReason
        : submitError.message;

    showError(message || 'Unable to process payment. Please try again.');
    trackEvent('paze_submit_error', {
      errorCode: 'submitResult' in submitError
        ? submitError.submitResult.errorCode
        : 'errorCode' in submitError
          ? submitError.errorCode
          : submitError.ErrorCode
    });
  }
};

async function verifyPaymentOnBackend(transactionData) {
  try {
    const response = await fetch('/api/verify-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(transactionData)
    });
    
    return await response.json();
  } catch (error) {
    console.error('Backend verification failed:', error);
    return { success: false, error: error.message };
  }
}

// Solution 2: Implement retry logic for transient failures
async function handleAuthorizationWithRetry(data) {
  if (data.state !== 'Authorised') {
    // Use onSubmitError for submission-level failures; check state for authorisation outcome
    trackEvent('paze_authorization_failed', {
      state: data.state,
      providerResponseCode: data.providerResponseCode
    });
  }
}
```

## Debugging tools and techniques

To get more detailed information about errors, enable verbose logging and run environment checks before reproducing an issue:

```typescript
// Enable comprehensive logging
window.pazeDebug = {
  logLevel: 'verbose',
  logPaymentData: false, // Never log actual payment tokens
  logNetworkRequests: true,
  logPresentment: true,
  logDecryption: false // Security: never log decryption details
};

// Debug helper functions
const debugPaze = {
  checkEnvironment() {
    console.group('🔍 Paze Environment Check');
    console.log('Protocol:', location.protocol);
    console.log('Hostname:', location.hostname);
    console.log('User Agent:', navigator.userAgent);
    console.log('Paze SDK Available:', !!(window.DIGITAL_WALLET_SDK));
    console.log('Environment:', pxpSdk.environment);
    console.log('Currency:', pxpSdk.getConfig().transactionData.currency);
    console.groupEnd();
  },
  
  logConfiguration(config) {
    console.group('⚙️ Paze Configuration');
    console.log('Button Render Mode:', config.buttonRenderMode);
    console.log('Email Address:', config.emailAddress ? '***@***.***' : 'Not provided'); // Masked
    console.log('Phone Number:', config.phoneNumber ? '***-***-****' : 'Not provided'); // Masked
    console.log('Billing Preference:', config.billingPreference);
    console.log('Accepted Card Networks:', config.acceptedPaymentCardNetworks);
    console.log('Perform AVS:', config.performAVS);
    console.log('Cobrand:', config.cobrand);
    console.groupEnd();
  },
  
  logSessionConfig() {
    console.group('📋 Session Paze Configuration');
    const pazeConfig = pxpSdk.getConfig().session?.allowedFundingTypes?.wallets?.paze;
    
    if (!pazeConfig) {
      console.error('❌ Paze not configured in session');
    } else {
      console.log('✅ Paze configuration present');
      console.log('Client ID:', pazeConfig.clientId ? 'Present' : 'Missing');
      console.log('Profile ID:', pazeConfig.profileId ? 'Present' : 'Not set');
    }
    console.groupEnd();
  },
  
  testPazeSDK() {
    console.group('Paze SDK test');
    
    if (!window.DIGITAL_WALLET_SDK) {
      console.error('Paze SDK not loaded');
      console.log('Check: Script tag is present in HTML');
      console.log('Check: Network requests for Paze SDK');
    } else {
      console.log('Paze SDK loaded');
      console.log('DIGITAL_WALLET_SDK.canCheckout available:', typeof window.DIGITAL_WALLET_SDK.canCheckout === 'function');
      console.log('DIGITAL_WALLET_SDK.checkout available:', typeof window.DIGITAL_WALLET_SDK.checkout === 'function');
    }
    
    console.groupEnd();
  },
  
  logPresentmentFlow(config) {
    console.group('Paze Presentment Flow');
    console.log('Email:', config.emailAddress ? '***@***.***' : 'Not provided');
    console.log('Phone:', config.phoneNumber ? '***-***-****' : 'Not provided');
    console.log('Render mode:', config.buttonRenderMode);
    
    console.log('Waiting for presentment resolution...');
    console.groupEnd();
  }
};

// Use in your implementation
debugPaze.checkEnvironment();
debugPaze.logSessionConfig();
debugPaze.testPazeSDK();
debugPaze.logConfiguration(pazeConfig);
```

## Prevention and best practices

### Proactive error prevention

Run pre-flight checks before creating the Paze button to catch configuration issues early:

```typescript
// Pre-flight checks before initializing Paze
async function performPreflightChecks() {
  const checks = [
    {
      name: 'HTTPS check',
      test: () => location.protocol === 'https:' || location.hostname === 'localhost',
      fix: 'Ensure your site is served over HTTPS'
    },
    {
      name: 'Paze SDK loaded',
      test: () => !!(window.DIGITAL_WALLET_SDK && window.DIGITAL_WALLET_SDK.canCheckout),
      fix: 'Ensure Paze SDK script is loaded'
    },
    {
      name: 'Session Paze configuration',
      test: () => {
        const pazeConfig = pxpSdk.getConfig().session?.allowedFundingTypes?.wallets?.paze;
        return pazeConfig?.clientId;
      },
      fix: 'Enable Paze in Unity Portal and ensure it\'s in session response'
    },
    {
      name: 'Currency is USD',
      test: () => pxpSdk.getConfig().transactionData.currency === 'USD',
      fix: 'Paze only supports USD currency'
    },
    {
      name: 'Identity fields for dynamic presentment',
      test: () => {
        if (pazeConfig.buttonRenderMode !== 'dynamic') return true;

        const email = pazeConfig.emailAddress;
        const phone = pazeConfig.phoneNumber;
        return email &&
               /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) &&
               email.length <= 128 &&
               phone &&
               /^1\d{10}$/.test(phone);
      },
      fix: 'Dynamic presentment requires valid emailAddress and phoneNumber (US E.164 without +, 11 digits)'
    },
    {
      name: 'Valid transaction amount',
      test: () => {
        const amount = pxpSdk.getConfig().transactionData.amount;
        return !isNaN(amount) && amount > 0;
      },
      fix: 'Ensure transaction amount is valid'
    },
    {
      name: 'Browser compatibility',
      test: () => checkBrowserCompatibility(),
      fix: 'Use a supported browser (Chrome 80+, Safari 13.1+, Firefox 75+, Edge 80+)'
    }
  ];
  
  const results = [];
  for (const check of checks) {
    const passed = await check.test();
    results.push({ name: check.name, passed, fix: check.fix });
    
    if (!passed) {
      console.warn(`${check.name}: ${check.fix}`);
    } else {
      console.log(`${check.name}`);
    }
  }
  
  return results.every(result => result.passed);
}

function checkBrowserCompatibility() {
  const ua = navigator.userAgent;
  
  // Chrome 80+
  if (/Chrome\/(\d+)/.test(ua)) {
    return parseInt(RegExp.$1) >= 80;
  }
  
  // Safari 13.1+
  if (/Version\/(\d+)\.(\d+)/.test(ua) && /Safari/.test(ua)) {
    const major = parseInt(RegExp.$1);
    const minor = parseInt(RegExp.$2);
    return major > 13 || (major === 13 && minor >= 1);
  }
  
  // Firefox 75+
  if (/Firefox\/(\d+)/.test(ua)) {
    return parseInt(RegExp.$1) >= 75;
  }
  
  // Edge 80+
  if (/Edg\/(\d+)/.test(ua)) {
    return parseInt(RegExp.$1) >= 80;
  }
  
  return false;
}

// Use before initialisation
async function initializePaze() {
  const preflightPassed = await performPreflightChecks();
  
  if (preflightPassed) {
    const pazeButton = pxpSdk.create('paze-button', pazeConfig);
    pazeButton.mount('paze-container');
  } else {
    console.warn('Preflight checks failed, showing alternative payment methods');
    showFallbackPaymentOptions();
  }
}
```

### Monitoring and alerting

Wire Paze callbacks into your error monitoring and analytics services:

```typescript
// Error monitoring setup
const errorMonitoring = {
  captureError(error, context) {
    // Send to your error tracking service
    const errorData = {
      message: error.message,
      name: error.name,
      errorCode: error.ErrorCode,
      stack: error.stack,
      context: context,
      userAgent: navigator.userAgent,
      url: location.href,
      timestamp: new Date().toISOString(),
      userId: getCurrentUserId(),
      sessionId: getSessionId()
    };
    
    // Example: Send to Sentry, LogRocket, etc.
    this.sendToErrorService(errorData);
    
    // Alert on critical errors
    if (this.isCriticalError(error)) {
      this.alertOpsTeam(errorData);
    }
  },
  
  isCriticalError(error) {
    const criticalCodes = [
      'SDK1200', // SDK load failed
      'SDK1206', // Initialisation failed
      'SDK1233'  // Decryption failed
    ];
    
    return criticalCodes.includes(error.ErrorCode);
  },
  
  trackPazeEvent(eventType, data) {
    // Track Paze specific events
    const eventData = {
      type: eventType,
      data: data,
      timestamp: new Date().toISOString(),
      sessionId: getSessionId()
    };
    
    this.sendToAnalytics(eventData);
  },
  
  sendToErrorService(data) {
    // Example: Sentry
    if (window.Sentry) {
      Sentry.captureException(new Error(data.message), {
        extra: data
      });
    }
  },
  
  sendToAnalytics(data) {
    // Example: Google Analytics
    if (window.gtag) {
      gtag('event', data.type, data.data);
    }
  },
  
  alertOpsTeam(errorData) {
    // Send alert to operations team
    fetch('/api/alerts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorData)
    });
  }
};

// Integration with Paze component
const config = {
  onError: (error) => {
    errorMonitoring.captureError(error, { component: 'Paze' });
    handlePazeError(error);
  },
  
  onPresentmentResolved: (isVisible) => {
    errorMonitoring.trackPazeEvent('presentment_resolved', {
      isVisible
    });
  },
  
  onCheckoutComplete: (result) => {
    errorMonitoring.trackPazeEvent('checkout_complete', {
      sessionId: result.checkoutDecodedResponse?.sessionId,
      cardNetwork: result.checkoutDecodedResponse?.maskedCard?.paymentCardNetwork
    });
    return true;
  },
  
  onPostAuthorisation: (data) => {
    errorMonitoring.trackPazeEvent('payment_completed', {
      success: data.state === 'Authorised',
      merchantTransactionId: data.merchantTransactionId
    });
  }
};
```