3D Secure Integration Example

This example demonstrates how to implement 3D Secure (3DS) authentication with the PaySight Widget, including handling the complete 3DS flow and providing appropriate user feedback.

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 - 3DS Integration</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;
    }

    .status-message {
      padding: 12px 16px;
      border-radius: 6px;
      margin-bottom: 16px;
      display: none;
      font-size: 14px;
    }

    .error {
      background-color: #fee2e2;
      border: 1px solid #ef4444;
      color: #b91c1c;
    }

    .success {
      background-color: #dcfce7;
      border: 1px solid #22c55e;
      color: #15803d;
    }

    .info {
      background-color: #e0f2fe;
      border: 1px solid #0ea5e9;
      color: #0369a1;
    }

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

    .progress-steps {
      display: flex;
      justify-content: space-between;
      margin-bottom: 24px;
      position: relative;
    }

    .progress-steps::before {
      content: '';
      position: absolute;
      top: 14px;
      left: 0;
      right: 0;
      height: 2px;
      background-color: #e5e7eb;
      z-index: 0;
    }

    .step {
      position: relative;
      z-index: 1;
      background-color: white;
      padding: 0 12px;
      text-align: center;
    }

    .step-number {
      width: 30px;
      height: 30px;
      border-radius: 50%;
      background-color: #e5e7eb;
      color: #64748b;
      display: flex;
      align-items: center;
      justify-content: center;
      margin: 0 auto 8px;
      font-weight: 500;
      transition: all 0.2s ease;
    }

    .step-label {
      font-size: 14px;
      color: #64748b;
      transition: all 0.2s ease;
    }

    .step.active .step-number {
      background-color: #3b82f6;
      color: white;
    }

    .step.active .step-label {
      color: #0f172a;
      font-weight: 500;
    }

    .step.complete .step-number {
      background-color: #22c55e;
      color: white;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>Secure Payment</h1>
      <p>Complete your payment with 3D Secure authentication</p>
    </div>

    <div class="progress-steps">
      <div class="step active" id="step-1">
        <div class="step-number">1</div>
        <div class="step-label">Card Details</div>
      </div>
      <div class="step" id="step-2">
        <div class="step-number">2</div>
        <div class="step-label">3D Secure</div>
      </div>
      <div class="step" id="step-3">
        <div class="step-number">3</div>
        <div class="step-label">Confirmation</div>
      </div>
    </div>

    <div id="info-message" class="status-message info"></div>
    <div id="error-message" class="status-message error"></div>
    <div id="success-message" class="status-message success"></div>
    
    <div id="widget-container" class="widget-container"></div>
  </div>

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

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

    // Initialize widget
    const widget = PaySightSDK.createWidget({
      targetId: 'widget-container',
      config,
      onReady: handleWidgetReady,
      onError: handleError,
      onMessage: handleMessage
    });

    // Widget ready handler
    function handleWidgetReady() {
      console.log('Widget is ready');
      updateProgress('step-1');
    }

    // Error handler
    function handleError(error) {
      console.error('Widget error:', error);
      showError(error.message);
      updateProgress('step-1');
    }

    // Message handler
    function handleMessage(message) {
      console.log('Message received:', message);

      switch (message.type) {
        case 'PAYMENT_START':
          hideMessages();
          showInfo('Processing payment...');
          break;

        case 'PAYMENT_3DS_START':
          hideMessages();
          showInfo('Starting 3D Secure verification...');
          updateProgress('step-2');
          break;

        case 'PAYMENT_3DS_SUCCESS':
          hideMessages();
          showInfo('3D Secure verification successful, completing payment...');
          break;

        case 'PAYMENT_3DS_ERROR':
          handlePaymentError(message.payload);
          updateProgress('step-1');
          break;

        case 'PAYMENT_3DS_FAILURE':
          handlePaymentError({
            message: '3D Secure verification failed. Please try again.'
          });
          updateProgress('step-1');
          break;

        case 'PAYMENT_SUCCESS':
          handlePaymentSuccess(message.payload);
          updateProgress('step-3');
          break;

        case 'PAYMENT_ERROR':
          handlePaymentError(message.payload);
          updateProgress('step-1');
          break;
      }
    }

    // Payment success handler
    function handlePaymentSuccess(payload) {
      const { transactionId, amount, currency } = payload;
      const formattedAmount = new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency
      }).format(amount / 100);

      showSuccess(
        `Payment successful! Amount: ${formattedAmount}, Transaction ID: ${transactionId}`
      );

      // Optional: Redirect to success page after delay
      setTimeout(() => {
        // window.location.href = '/payment-success';
      }, 3000);
    }

    // Payment error handler
    function handlePaymentError(payload) {
      showError(payload.message);
    }

    // Progress management
    function updateProgress(stepId) {
      const steps = ['step-1', 'step-2', 'step-3'];
      const currentIndex = steps.indexOf(stepId);

      steps.forEach((step, index) => {
        const element = document.getElementById(step);
        element.classList.remove('active', 'complete');

        if (index === currentIndex) {
          element.classList.add('active');
        } else if (index < currentIndex) {
          element.classList.add('complete');
        }
      });
    }

    // UI Helper functions
    function showSuccess(message) {
      const element = document.getElementById('success-message');
      element.textContent = message;
      element.style.display = 'block';
      hideOtherMessages('success-message');
    }

    function showError(message) {
      const element = document.getElementById('error-message');
      element.textContent = message;
      element.style.display = 'block';
      hideOtherMessages('error-message');
    }

    function showInfo(message) {
      const element = document.getElementById('info-message');
      element.textContent = message;
      element.style.display = 'block';
      hideOtherMessages('info-message');
    }

    function hideMessages() {
      document.getElementById('success-message').style.display = 'none';
      document.getElementById('error-message').style.display = 'none';
      document.getElementById('info-message').style.display = 'none';
    }

    function hideOtherMessages(currentId) {
      const messageIds = ['success-message', 'error-message', 'info-message'];
      messageIds
        .filter(id => id !== currentId)
        .forEach(id => {
          document.getElementById(id).style.display = 'none';
        });
    }
  </script>
</body>
</html>

Key Components

1. Enable 3DS

const config = {
  productId: YOUR_PRODUCT_ID,
  sessionId: 'unique-session-id',
  amount: 2999,
  environment: 'production',
  threeDSRequired: true // Enable 3DS authentication
};

2. Handle 3DS Events

function handleMessage(message) {
  switch (message.type) {
    case 'PAYMENT_3DS_START':
      // 3DS verification started
      showLoadingUI();
      break;
      
    case 'PAYMENT_3DS_SUCCESS':
      // 3DS verification successful
      hideLoadingUI();
      break;
      
    case 'PAYMENT_3DS_ERROR':
      // 3DS verification error
      handleError(message.payload);
      break;
      
    case 'PAYMENT_3DS_FAILURE':
      // 3DS verification failed
      handleFailure(message.payload);
      break;
  }
}

3. Progress Tracking

function updateProgress(stepId) {
  const steps = ['step-1', 'step-2', 'step-3'];
  const currentIndex = steps.indexOf(stepId);

  steps.forEach((step, index) => {
    const element = document.getElementById(step);
    element.classList.remove('active', 'complete');

    if (index === currentIndex) {
      element.classList.add('active');
    } else if (index < currentIndex) {
      element.classList.add('complete');
    }
  });
}

Implementation Steps

  1. Enable 3DS

    • Set threeDSRequired: true in configuration
    • Ensure proper environment setup
  2. Handle 3DS Flow

    • Implement all 3DS event handlers
    • Show appropriate loading states
    • Handle success and error cases
  3. Add Progress Tracking

    • Create progress indicator UI
    • Update progress based on events
    • Show current step to user
  4. Implement Error Handling

    • Handle 3DS-specific errors
    • Show user-friendly error messages
    • Provide recovery options
  5. Add User Feedback

    • Show loading indicators
    • Display progress messages
    • Confirm successful completion

Best Practices

  1. Clear User Communication
function showUserFeedback(step) {
  const messages = {
    'PAYMENT_3DS_START': 'Verifying your card with 3D Secure...',
    'PAYMENT_3DS_SUCCESS': '3D Secure verification successful',
    'PAYMENT_3DS_ERROR': 'Unable to complete 3D Secure verification'
  };
  
  showMessage(messages[step]);
}
  1. Handle Timeouts
const TIMEOUT_DURATION = 60000; // 60 seconds

let timeoutId = setTimeout(() => {
  handleTimeout();
}, TIMEOUT_DURATION);

function handleTimeout() {
  showError('3D Secure verification timed out. Please try again.');
  updateProgress('step-1');
}
  1. Implement Recovery
function handleError(error) {
  if (error.code === '3DS_UNAVAILABLE') {
    // Fallback to non-3DS payment if allowed
    retryWithout3DS();
  } else {
    // Show error and reset form
    showError(error.message);
    resetForm();
  }
}

Next Steps