Skip to main content

Error Handling Example

This example demonstrates comprehensive error handling strategies for the Paysight Widget, including handling various error types, providing user feedback, and implementing recovery mechanisms.

Complete Implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Paysight Widget - Error Handling</title>

  <style>
    body {
      font-family: system-ui, -apple-system, sans-serif;
      line-height: 1.5;
      margin: 0;
      padding: 20px;
      background-color: #f8fafc;
    }

    .container {
      max-width: 600px;
      margin: 40px auto;
      background-color: white;
      border-radius: 8px;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
      padding: 24px;
    }

    .header {
      text-align: center;
      margin-bottom: 24px;
    }

    .header h1 {
      margin: 0;
      color: #0f172a;
      font-size: 24px;
    }

    .header p {
      margin: 8px 0 0;
      color: #64748b;
    }

    .widget-container {
      border: 1px solid #e5e7eb;
      border-radius: 6px;
      padding: 20px;
      margin-bottom: 20px;
    }

    .error-container {
      margin-bottom: 20px;
    }

    .error-message {
      background-color: #fee2e2;
      border: 1px solid #ef4444;
      color: #b91c1c;
      padding: 12px 16px;
      border-radius: 6px;
      margin-bottom: 12px;
      font-size: 14px;
      display: none;
    }

    .error-details {
      background-color: #fff1f2;
      border: 1px solid #fecdd3;
      color: #881337;
      padding: 12px 16px;
      border-radius: 6px;
      font-size: 13px;
      font-family: monospace;
      white-space: pre-wrap;
      display: none;
      margin-top: 8px;
    }

    .error-actions {
      display: none;
      margin-top: 12px;
    }

    .error-action-button {
      background-color: #ef4444;
      color: white;
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      font-size: 14px;
      cursor: pointer;
      transition: background-color 0.2s;
    }

    .error-action-button:hover {
      background-color: #dc2626;
    }

    .error-action-button + .error-action-button {
      margin-left: 8px;
      background-color: #6b7280;
    }

    .error-action-button + .error-action-button:hover {
      background-color: #4b5563;
    }

    .debug-panel {
      margin-top: 24px;
      padding: 16px;
      background-color: #f8fafc;
      border: 1px solid #e2e8f0;
      border-radius: 6px;
    }

    .debug-panel h3 {
      margin: 0 0 12px;
      color: #0f172a;
      font-size: 16px;
    }

    .debug-log {
      background-color: white;
      border: 1px solid #e2e8f0;
      border-radius: 4px;
      padding: 12px;
      max-height: 200px;
      overflow-y: auto;
      font-family: monospace;
      font-size: 13px;
      line-height: 1.4;
    }

    .debug-entry {
      margin-bottom: 8px;
      padding-bottom: 8px;
      border-bottom: 1px solid #f1f5f9;
    }

    .debug-entry:last-child {
      margin-bottom: 0;
      padding-bottom: 0;
      border-bottom: none;
    }

    .debug-timestamp {
      color: #64748b;
      font-size: 12px;
    }

    .debug-type {
      display: inline-block;
      padding: 2px 6px;
      border-radius: 4px;
      font-size: 12px;
      font-weight: 500;
      margin: 0 8px;
    }

    .debug-type.error {
      background-color: #fee2e2;
      color: #b91c1c;
    }

    .debug-type.warning {
      background-color: #fef3c7;
      color: #92400e;
    }

    .debug-type.info {
      background-color: #e0f2fe;
      color: #0369a1;
    }

    .debug-message {
      color: #0f172a;
    }

    .retry-button {
      display: none;
      margin-top: 16px;
      background-color: #3b82f6;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 6px;
      font-size: 14px;
      cursor: pointer;
      transition: background-color 0.2s;
    }

    .retry-button:hover {
      background-color: #2563eb;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>Secure Payment</h1>
      <p>Complete your payment with error handling demonstration</p>
    </div>

    <div class="error-container">
      <div id="error-message" class="error-message"></div>
      <div id="error-details" class="error-details"></div>
      <div id="error-actions" class="error-actions">
        <button class="error-action-button" onclick="retryPayment()">
          Retry Payment
        </button>
        <button class="error-action-button" onclick="resetWidget()">
          Reset Form
        </button>
      </div>
    </div>

    <div id="widget-container" class="widget-container"></div>
    
    <button id="retry-button" class="retry-button" onclick="retryPayment()">
      Try Again
    </button>

    <div class="debug-panel">
      <h3>Debug Log</h3>
      <div id="debug-log" class="debug-log"></div>
    </div>
  </div>

  <!-- Add the Paysight Widget SDK -->
  <script src="https://payment.paysight.io/widget-sdk.js"></script>

  <script>
    // Error types and messages
    const ErrorTypes = {
      VALIDATION: 'VALIDATION_ERROR',
      NETWORK: 'NETWORK_ERROR',
      PAYMENT: 'PAYMENT_ERROR',
      WIDGET: 'ERROR',
      SYSTEM: 'SYSTEM_ERROR'
    };

    const ErrorMessages = {
      [ErrorTypes.VALIDATION]: {
        title: 'Invalid Input',
        message: 'Please check your input and try again.',
        recoverable: true
      },
      [ErrorTypes.NETWORK]: {
        title: 'Connection Error',
        message: 'Unable to connect to the payment service.',
        recoverable: true
      },
      [ErrorTypes.PAYMENT]: {
        title: 'Payment Failed',
        message: 'Unable to process your payment.',
        recoverable: true
      },
      [ErrorTypes.WIDGET]: {
        title: 'Widget Error',
        message: 'The payment widget encountered an error.',
        recoverable: false
      },
      [ErrorTypes.SYSTEM]: {
        title: 'System Error',
        message: 'An unexpected error occurred.',
        recoverable: false
      }
    };

    // Widget configuration
    const config = {
      productId: YOUR_PRODUCT_ID,
      sessionId: \`session_\${Date.now()}\`,
      amount: 2999,
      environment: 'production',
      threeDSRequired: true,
      ecom: true,
      currency: 'USD',
      locale: 'en-US'
    };

    // Widget instance
    let widget;
    let retryCount = 0;
    const MAX_RETRIES = 3;
    const RETRY_DELAY = 2000;

    // Initialize widget
    function initializeWidget() {
      try {
        widget = PaysightSDK.createWidget({
          targetId: 'widget-container',
          config,
          onReady: handleWidgetReady,
          onError: handleError,
          onMessage: handleWidgetMessage
        });

        logDebug('Widget initialized', 'info');
      } catch (error) {
        handleError({
          type: ErrorTypes.SYSTEM,
          message: 'Failed to initialize widget',
          details: error
        });
      }
    }

    // Widget ready handler
    function handleWidgetReady() {
      logDebug('Widget ready', 'info');
      hideError();
      hideRetryButton();
    }

    // Error handler
    function handleError(error) {
      logDebug(\`Error: \${error.message}\`, 'error');
      
      const errorType = getErrorType(error);
      const errorInfo = ErrorMessages[errorType];
      
      showError(
        errorInfo.title,
        error.message || errorInfo.message,
        error.details,
        errorInfo.recoverable
      );

      if (errorInfo.recoverable && retryCount < MAX_RETRIES) {
        showRetryButton();
      }
    }

    // Message handler for widget events
    function handleWidgetMessage(message) {
      switch (message.type) {
        case 'VALIDATION_ERROR':
          handleValidationError(message.payload);
          break;

        case 'PAYMENT_ERROR':
          handlePaymentError(message.payload);
          break;

        case 'NETWORK_ERROR':
          handleNetworkError(message.payload);
          break;

        case 'PAYMENT_3DS_ERROR':
          handle3DSError(message.payload);
          break;

        case 'PAYMENT_SUCCESS':
          handlePaymentSuccess(message.payload);
          break;
      }
    }

    // Error type handlers
    function handlePaymentError(payload) {
      handleError({
        type: ErrorTypes.PAYMENT,
        message: payload.message,
        details: payload
      });
    }

    function handleValidationError(payload) {
      handleError({
        type: ErrorTypes.VALIDATION,
        message: 'Please check your card details',
        details: payload
      });
    }

    function handleNetworkError(payload) {
      handleError({
        type: ErrorTypes.NETWORK,
        message: 'Connection failed. Please check your internet connection.',
        details: payload
      });
    }

    function handle3DSError(payload) {
      handleError({
        type: ErrorTypes.SYSTEM,
        message: '3D Secure Error',
        details: payload
      });
    }

    function handlePaymentSuccess(payload) {
      handleError({
        type: ErrorTypes.SYSTEM,
        message: 'Payment Successful',
        details: payload
      });
    }

    // Helper functions
    function getErrorType(error) {
      if (error.type) return error.type;
      if (error.code?.includes('VALIDATION')) return ErrorTypes.VALIDATION;
      if (error.code?.includes('NETWORK')) return ErrorTypes.NETWORK;
      if (error.code?.includes('PAYMENT')) return ErrorTypes.PAYMENT;
      if (error.code?.includes('WIDGET')) return ErrorTypes.WIDGET;
      return ErrorTypes.SYSTEM;
    }

    function showError(title, message, details, recoverable) {
      const errorMessage = document.getElementById('error-message');
      const errorDetails = document.getElementById('error-details');
      const errorActions = document.getElementById('error-actions');

      errorMessage.textContent = \`\${title}: \${message}\`;
      errorMessage.style.display = 'block';

      if (details) {
        errorDetails.textContent = JSON.stringify(details, null, 2);
        errorDetails.style.display = 'block';
      }

      if (recoverable) {
        errorActions.style.display = 'block';
      }
    }

    function hideError() {
      document.getElementById('error-message').style.display = 'none';
      document.getElementById('error-details').style.display = 'none';
      document.getElementById('error-actions').style.display = 'none';
    }

    function showRetryButton() {
      document.getElementById('retry-button').style.display = 'block';
    }

    function hideRetryButton() {
      document.getElementById('retry-button').style.display = 'none';
    }

    // Retry mechanism
    function retryPayment() {
      if (retryCount >= MAX_RETRIES) {
        handleError({
          type: ErrorTypes.SYSTEM,
          message: 'Maximum retry attempts reached',
          recoverable: false
        });
        return;
      }

      retryCount++;
      logDebug(\`Retrying payment (attempt \${retryCount})\`, 'info');

      setTimeout(() => {
        resetWidget();
        initializeWidget();
      }, RETRY_DELAY);
    }

    function resetWidget() {
      if (widget) {
        widget.destroy();
      }
      hideError();
      hideRetryButton();
      initializeWidget();
      retryCount = 0;
    }

    // Debug logging
    function logDebug(message, type = 'info') {
      const debugLog = document.getElementById('debug-log');
      const entry = document.createElement('div');
      entry.className = 'debug-entry';

      const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
      
      entry.innerHTML = \`
        <span class="debug-timestamp">\${timestamp}</span>
        <span class="debug-type \${type}">\${type.toUpperCase()}</span>
        <span class="debug-message">\${message}</span>
      \`;

      debugLog.insertBefore(entry, debugLog.firstChild);
    }

    // Initialize on load
    window.addEventListener('load', () => {
      initializeWidget();
    });
  </script>
</body>
</html>

Key Components

1. Error Types and Messages

const ErrorTypes = {
  VALIDATION: 'VALIDATION_ERROR',
  NETWORK: 'NETWORK_ERROR',
  PAYMENT: 'PAYMENT_ERROR',
  WIDGET: 'ERROR',
  SYSTEM: 'SYSTEM_ERROR'
};

const ErrorMessages = {
  [ErrorTypes.VALIDATION]: {
    title: 'Invalid Input',
    message: 'Please check your input and try again.',
    recoverable: true
  },
  // ... other error types
};

2. Error Handler

function handleError(error) {
  const errorType = getErrorType(error);
  const errorInfo = ErrorMessages[errorType];
  
  showError(
    errorInfo.title,
    error.message || errorInfo.message,
    error.details,
    errorInfo.recoverable
  );

  if (errorInfo.recoverable && retryCount < MAX_RETRIES) {
    showRetryButton();
  }
}

3. Retry Mechanism

function retryPayment() {
  if (retryCount >= MAX_RETRIES) {
    handleError({
      type: ErrorTypes.SYSTEM,
      message: 'Maximum retry attempts reached',
      recoverable: false
    });
    return;
  }

  retryCount++;
  setTimeout(() => {
    resetWidget();
    initializeWidget();
  }, RETRY_DELAY);
}

Implementation Steps

  1. Define Error Types
    • Categorize different error scenarios
    • Define error messages and recovery options
    • Set up error constants
  2. Implement Error Handlers
    • Create main error handler
    • Add specific error type handlers
    • Set up error recovery logic
  3. Add Retry Mechanism
    • Implement retry counter
    • Add delay between retries
    • Handle maximum retry limit
  4. Create Debug Interface
    • Add debug logging
    • Create debug UI panel
    • Log all events and errors
  5. Add User Feedback
    • Show error messages
    • Display error details
    • Provide retry options

Best Practices

  1. Categorize Errors
function getErrorType(error) {
  // Network errors
  if (error instanceof TypeError || error.name === 'NetworkError') {
    return ErrorTypes.NETWORK;
  }
  
  // Validation errors
  if (error.code?.startsWith('VALIDATION_')) {
    return ErrorTypes.VALIDATION;
  }
  
  // Payment errors
  if (error.code?.startsWith('PAYMENT_')) {
    return ErrorTypes.PAYMENT;
  }
  
  return ErrorTypes.SYSTEM;
}
  1. Implement Circuit Breaker
class CircuitBreaker {
  constructor() {
    this.failures = 0;
    this.lastFailure = null;
    this.state = 'CLOSED';
  }

  recordFailure() {
    this.failures++;
    this.lastFailure = Date.now();
    
    if (this.failures >= MAX_FAILURES) {
      this.state = 'OPEN';
    }
  }

  canRetry() {
    if (this.state === 'OPEN') {
      const timeSinceLastFailure = Date.now() - this.lastFailure;
      if (timeSinceLastFailure > RESET_TIMEOUT) {
        this.reset();
        return true;
      }
      return false;
    }
    return true;
  }

  reset() {
    this.failures = 0;
    this.lastFailure = null;
    this.state = 'CLOSED';
  }
}
  1. Implement Error Recovery
async function attemptRecovery(error) {
  const recoveryStrategies = {
    [ErrorTypes.NETWORK]: async () => {
      await checkConnectivity();
      return retryPayment();
    },
    [ErrorTypes.VALIDATION]: () => {
      resetForm();
      return Promise.resolve();
    },
    [ErrorTypes.PAYMENT]: async () => {
      await validatePaymentState();
      return retryPayment();
    }
  };

  const strategy = recoveryStrategies[error.type];
  if (strategy) {
    return strategy();
  }
  
  throw new Error('No recovery strategy available');
}

Next Steps

I