Skip to main content

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

I