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