Smart Fields Testing & Demos

Test Smart Fields safely in the Localpayment Sandbox. Simulate payments and validate your integration before going live.

Testing your Smart Fields integration is a critical step before going live. The Localpayment Sandbox allows you to simulate real-world scenarios securely, helping you validate both frontend and backend flows.

Stage Environment

Utilize the sandbox environment with test API keys for your integration:

const localpayment = LP({
    clientCode: 'your_stage_client_code',
    apiKey: 'your_stage_api_key'
});

Ensure you are using the correct API Key and the Stage SDK URL in your application:

<script src="https://sdk.stage.localpayment.com/localpayment-sdk.min.js"></script>
  • API Keys: Generate separate API Keys for Stage and Production environments.
  • Domain Validation: Session initialization will fail if requests come from unregistered domains, triggering an “Invalid Source Domain” error.
  • Simulation Only: Sandbox transactions simulate payment flows without moving real money.

Test Card Numbers

Use the following test card numbers to simulate different payment scenarios:

BrandCard NumberDescription
Visa4111 1111 1111 1111Always succeeds
Mastercard5555 5555 5555 4444Always succeeds
Invalid Card4242 4242 4242 4241Fails validation
Declined4000 0000 0000 0127Transaction declined
  • Expiration Date: Use any valid future date.
  • CVV: Any 3-digit CVV is acceptable.
  • Cardholder Name: Any name is acceptable.

Error Simulation

Test error handling by simulating potential issues:

Invalid Card Number

  • Scenario: Use 4242424242424241.
  • Outcome: Triggers a change event with isValid: false.

Expired Card

  • Scenario: Use a past expiration date, e.g., 12/2020.
  • Outcome: Tokenization fails with expiration error.

Session Expiration

  • Scenario: Wait over 10 minutes after initializeSession()
  • Outcome: Tokenization fails due to session expiration.

Network Errors

  • Scenario: Disable internet connection temporarily.
  • Outcome: Tokenization fails with a network error.

Ensure your integration listens and responds to error events, offering appropriate user feedback and retry options.


Complete Test Example

Validate your integration with a complete example page:

🗒️

Run the example from localhost. Add localhost as an allowed domain for the Smart Fields API Key to avoid domain validation errors.

Example Code
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Smart Fields SDK Demo</title>
  <style>
      * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
      }

      body {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
          background-color: #f8fafc;
          color: #334155;
          line-height: 1.6;
      }

      .container {
          max-width: 800px;
          margin: 0 auto;
          padding: 20px;
      }

      .header {
          text-align: center;
          margin-bottom: 40px;
          padding: 40px 0;
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          color: white;
          border-radius: 12px;
      }

      .header h1 {
          font-size: 2.5rem;
          margin-bottom: 10px;
          font-weight: 700;
      }

      .header p {
          font-size: 1.1rem;
          opacity: 0.9;
      }

      .card {
          background: white;
          padding: 30px;
          border-radius: 12px;
          box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
          margin-bottom: 30px;
          position: relative;
          transition: opacity 0.3s ease;
      }

      .card.disabled {
          opacity: 0.6;
          pointer-events: none;
      }

      .form-group {
          margin-bottom: 20px;
      }

      .form-label {
          display: block;
          margin-bottom: 8px;
          font-weight: 600;
          color: #374151;
      }

      .field-container {
          border: 2px solid #e5e7eb;
          border-radius: 8px;
          transition: border-color 0.2s;
          background: white;
          min-height: 46px;
      }

      .field-container:focus-within {
          border-color: #667eea;
          box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
      }

      .field-container.error {
          border-color: #ef4444;
      }

      .field-container.valid {
          border-color: #10b981;
      }

      .field-row {
          display: flex;
          gap: 20px;
      }

      .field-row .form-group {
          flex: 1;
      }

      .button {
          background: #667eea;
          color: white;
          border: none;
          padding: 12px 24px;
          border-radius: 8px;
          font-size: 16px;
          font-weight: 600;
          cursor: pointer;
          transition: all 0.2s;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 8px;
      }

      .button:hover:not(:disabled) {
          background: #5b6fd8;
          transform: translateY(-1px);
          box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
      }

      .button:disabled {
          background: #9ca3af;
          cursor: not-allowed;
          transform: none;
          box-shadow: none;
      }

      .button.secondary {
          background: #6b7280;
      }

      .button.secondary:hover:not(:disabled) {
          background: #5b6471;
      }

      .button.danger {
          background: #ef4444;
      }

      .button.danger:hover:not(:disabled) {
          background: #dc2626;
      }

      .button-group {
          display: flex;
          gap: 12px;
          margin-top: 30px;
          flex-wrap: wrap;
      }

      .status-indicator {
          width: 12px;
          height: 12px;
          border-radius: 50%;
          background: #d1d5db;
          display: inline-block;
          margin-right: 8px;
      }

      .status-indicator.valid {
          background: #10b981;
      }

      .status-indicator.error {
          background: #ef4444;
      }

      .status-indicator.warning {
          background: #f59e0b;
      }

      .field-status {
          display: flex;
          align-items: center;
          font-size: 14px;
          margin-top: 8px;
          opacity: 0.8;
      }

      .output {
          background: #f8fafc;
          border: 1px solid #e5e7eb;
          border-radius: 8px;
          padding: 20px;
          margin-top: 20px;
          display: none;
      }

      .output.success {
          background: #f0fdf4;
          border-color: #bbf7d0;
          color: #166534;
      }

      .output.error {
          background: #fef2f2;
          border-color: #fecaca;
          color: #991b1b;
      }

      .output.warning {
          background: #fffbeb;
          border-color: #fed7aa;
          color: #92400e;
      }

      .output h3 {
          margin-bottom: 15px;
          font-size: 18px;
      }

      .output-data {
          font-family: 'Monaco', 'Consolas', monospace;
          font-size: 14px;
          background: rgba(0, 0, 0, 0.05);
          padding: 15px;
          border-radius: 6px;
          white-space: pre-wrap;
          word-break: break-all;
      }

      .loading {
          opacity: 0.7;
          pointer-events: none;
      }

      .spinner {
          width: 16px;
          height: 16px;
          border: 2px solid transparent;
          border-top: 2px solid currentColor;
          border-radius: 50%;
          animation: spin 1s linear infinite;
      }

      @keyframes spin {
          to { transform: rotate(360deg); }
      }

      .demo-info {
          background: #eff6ff;
          border: 1px solid #bfdbfe;
          border-radius: 8px;
          padding: 20px;
          margin-bottom: 30px;
      }

      .demo-info h3 {
          color: #1e40af;
          margin-bottom: 10px;
      }

      .test-cards {
          display: grid;
          grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
          gap: 15px;
          margin-top: 15px;
      }

      .test-card {
          background: white;
          padding: 15px;
          border-radius: 6px;
          border: 1px solid #d1d5db;
          cursor: pointer;
          transition: all 0.2s;
      }

      .test-card:hover {
          transform: translateY(-2px);
          box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
          border-color: #667eea;
      }

      .test-card strong {
          display: block;
          margin-bottom: 5px;
          color: #374151;
      }

      .test-card code {
          font-size: 12px;
          color: #6b7280;
      }

      .config-section {
          background: #fefce8;
          border: 1px solid #fef08a;
          border-radius: 8px;
          padding: 20px;
          margin-bottom: 30px;
      }

      .config-section h3 {
          color: #ca8a04;
          margin-bottom: 15px;
      }

      .config-input {
          width: 100%;
          padding: 10px;
          border: 1px solid #e5e7eb;
          border-radius: 6px;
          margin-bottom: 10px;
      }

      .init-status {
          padding: 10px;
          border-radius: 6px;
          margin-top: 10px;
          display: none;
      }

      .init-status.success {
          background: #f0fdf4;
          border: 1px solid #bbf7d0;
          color: #166534;
      }

      .init-status.error {
          background: #fef2f2;
          border: 1px solid #fecaca;
          color: #991b1b;
      }

      .init-status.warning {
          background: #fffbeb;
          border: 1px solid #fed7aa;
          color: #92400e;
      }

      .session-info {
          position: absolute;
          top: 15px;
          right: 15px;
          display: flex;
          align-items: center;
          gap: 8px;
          font-size: 14px;
          background: #f1f5f9;
          padding: 6px 12px;
          border-radius: 20px;
      }

      .session-indicator {
          width: 10px;
          height: 10px;
          border-radius: 50%;
          background: #d1d5db;
      }

      .session-indicator.active {
          background: #10b981;
          animation: pulse 2s infinite;
      }

      .session-indicator.expiring {
          background: #f59e0b;
      }

      .session-indicator.expired {
          background: #ef4444;
      }

      @keyframes pulse {
          0% { opacity: 1; }
          50% { opacity: 0.5; }
          100% { opacity: 1; }
      }

      @media (max-width: 600px) {
          .field-row {
              flex-direction: column;
              gap: 0;
          }
          
          .button-group {
              flex-direction: column;
          }

          .session-info {
              position: relative;
              top: 0;
              right: 0;
              margin-bottom: 15px;
              justify-content: center;
          }
      }
  </style>
</head>
<body>
  <div class="container">
      <div class="header">
          <h1>Smart Fields SDK</h1>
          <p>Secure payment tokenization demo</p>
      </div>

      <div class="config-section">
          <h3>⚙️ Configuration</h3>
          <div class="form-group">
              <label class="form-label" for="client-code">Client Code</label>
              <input type="text" id="client-code" class="config-input" placeholder="Enter your client code" value="your_client_code_here">
          </div>
          <div class="form-group">
              <label class="form-label" for="api-key">API Key</label>
              <input type="text" id="api-key" class="config-input" placeholder="Enter your API key" value="your_api_key_here">
          </div>
          <button id="init-btn" class="button">Initialize SDK</button>
          <div id="init-status" class="init-status"></div>
      </div>

      <div class="demo-info">
          <h3>🔒 Security Features</h3>
          <p>This demo showcases Localpayment's secure tokenization system. Card data is collected in isolated iframes and never touches your server, ensuring PCI DSS compliance.</p>
          
          <div class="test-cards">
              <div class="test-card" id="visa-test-card">
                  <strong>Visa</strong>
                  <code>4111 1111 1111 1111</code>
              </div>
              <div class="test-card" id="mastercard-test-card">
                  <strong>Mastercard</strong>
                  <code>5555 5555 5555 4444</code>
              </div>
          </div>
      </div>

      <div id="payment-card" class="card disabled">
          <div class="session-info">
              <span class="session-indicator" id="session-indicator"></span>
              <span id="session-status">Session: Not initialized</span>
          </div>

          <h2>Payment Information</h2>
          <form id="payment-form">
              <div class="form-group">
                  <label class="form-label" for="cardholder-name">Cardholder Name</label>
                  <input type="text" id="cardholder-name" class="config-input" placeholder="Enter cardholder name" value="John Doe">
              </div>

              <div class="form-group">
                  <label class="form-label" for="card-number">Card Number</label>
                  <div id="card-number-container" class="field-container"></div>
                  <div id="card-number-status" class="field-status">
                      <span class="status-indicator"></span>
                      <span class="status-text">Enter card number</span>
                  </div>
              </div>

              <div class="field-row">
                  <div class="form-group">
                      <label class="form-label" for="cvv">CVV</label>
                      <div id="cvv-container" class="field-container"></div>
                      <div id="cvv-status" class="field-status">
                          <span class="status-indicator"></span>
                          <span class="status-text">Enter CVV</span>
                      </div>
                  </div>

                  <div class="form-group">
                      <label class="form-label" for="expiry">Expiry Date</label>
                      <div id="exp-container" class="field-container"></div>
                      <div id="exp-status" class="field-status">
                          <span class="status-indicator"></span>
                          <span class="status-text">Enter MM-YYYY</span>
                      </div>
                  </div>
              </div>

              <div class="button-group">
                  <button type="button" id="process-btn" class="button" disabled>
                      <span class="button-text">Process Payment</span>
                  </button>
                  <button type="button" id="clear-btn" class="button secondary">
                      Clear Fields
                  </button>
                  <button type="button" id="reload-btn" class="button danger" style="display: none;">
                      Restart Demo
                  </button>
              </div>
          </form>

          <div id="output" class="output">
              <h3 id="output-title">Result</h3>
              <div id="output-data" class="output-data"></div>
          </div>
      </div>
  </div>

  <!-- Load Smart Fields SDK -->
  <script src="https://sdk.stage.localpayment.com/localpayment-sdk.min.js"></script>
  <script>
      'use strict';

      // =============================================================================
      // CONFIGURATION
      // =============================================================================
      
      // Application state
      let localpayment = null;
      let cardNumber = null;
      let cvv = null;
      let expiry = null;
      let fieldsReady = { pan: false, cvv: false, expiration: false };
      let isInitialized = false;
      let sessionExpiryTimer = null;
      let sessionExpiryTime = null;

      // DOM elements
      const initBtn = document.getElementById('init-btn');
      const initStatus = document.getElementById('init-status');
      const clientCodeInput = document.getElementById('client-code');
      const apiKeyInput = document.getElementById('api-key');
      const processBtn = document.getElementById('process-btn');
      const clearBtn = document.getElementById('clear-btn');
      const reloadBtn = document.getElementById('reload-btn');
      const output = document.getElementById('output');
      const outputTitle = document.getElementById('output-title');
      const outputData = document.getElementById('output-data');
      const cardholderNameInput = document.getElementById('cardholder-name');
      const paymentCard = document.getElementById('payment-card');
      const sessionIndicator = document.getElementById('session-indicator');
      const sessionStatus = document.getElementById('session-status');
      const visaTestCard = document.getElementById('visa-test-card');
      const mastercardTestCard = document.getElementById('mastercard-test-card');

      // =============================================================================
      // INITIALIZATION FUNCTIONS
      // =============================================================================

      /**
       * Initialize the Smart Fields SDK with provided credentials
       */
      async function initializePayment() {
          try {
              // Get configuration values
              const clientCode = clientCodeInput.value.trim();
              const apiKey = apiKeyInput.value.trim();
              
              // Validate configuration
              if (!clientCode || !apiKey) {
                  throw new Error('Client code and API key are required');
              }
              
              setInitStatus('Initializing SDK...', '');
              
              // Disable initialization button during process
              initBtn.disabled = true;
              
              // Initialize Smart Fields SDK
              localpayment = LP({
                  clientCode: clientCode,
                  apiKey: apiKey
              });

              // Create session with backend
              setInitStatus('Creating session...', '');
              await localpayment.createSession();

              // Create secure fields AFTER session is ready
              setInitStatus('Creating secure fields...', '');
              cardNumber = localpayment.create('pan');
              cvv = localpayment.create('cvv');
              expiry = localpayment.create('expiration');

              // Mount fields to containers
              cardNumber.mount('#card-number-container');
              cvv.mount('#cvv-container');
              expiry.mount('#exp-container');

              // Setup field event listeners
              setupFieldListeners();
              
              // Update UI state
              isInitialized = true;
              setInitStatus('SDK initialized successfully!', 'success');
              paymentCard.classList.remove('disabled');
              
              // Update session status display
              updateSessionStatusDisplay();
              
              // Start session expiry monitoring
              startSessionExpiryMonitoring();
              
              // Setup session expiration listener
              setupSessionExpirationListener();

          } catch (error) {
              console.error('Failed to initialize Smart Fields SDK:', error);
              setInitStatus(`Initialization failed: ${error.message}`, 'error');
              isInitialized = false;
              initBtn.disabled = false;
          }
      }

      /**
       * Update the session status display
       */
      function updateSessionStatusDisplay() {
          if (!localpayment) return;
          
          const sessionData = localpayment.getSessionData();
          if (sessionData && sessionData.expiresAt) {
              sessionExpiryTime = new Date(sessionData.expiresAt);
              updateSessionStatus();
          } else {
              sessionIndicator.className = 'session-indicator active';
              sessionStatus.textContent = 'Session: Active';
          }
      }

      /**
       * Start monitoring session expiry time
       */
      function startSessionExpiryMonitoring() {
          // Clear any existing timer
          if (sessionExpiryTimer) {
              clearInterval(sessionExpiryTimer);
          }
          
          // Get session data to determine expiry
          const sessionData = localpayment.getSessionData();
          if (sessionData && sessionData.expiresAt) {
              sessionExpiryTime = new Date(sessionData.expiresAt);
              updateSessionStatus();
              
              // Update session status every minute
              sessionExpiryTimer = setInterval(updateSessionStatus, 60000);
          }
      }

      /**
       * Update the session status indicator
       */
      function updateSessionStatus() {
          if (!sessionExpiryTime) return;
          
          const now = new Date();
          const timeRemaining = sessionExpiryTime - now;
          const minutesRemaining = Math.floor(timeRemaining / 60000);
          
          if (minutesRemaining <= 0) {
              // Session has expired
              sessionIndicator.className = 'session-indicator expired';
              sessionStatus.textContent = 'Session: Expired';
              handleSessionExpiration();
          } else if (minutesRemaining <= 5) {
              // Session expiring soon
              sessionIndicator.className = 'session-indicator expiring';
              sessionStatus.textContent = `Session: Expiring in ${minutesRemaining} min`;
              showReloadButton();
          } else {
              // Session is active
              sessionIndicator.className = 'session-indicator active';
              sessionStatus.textContent = `Session: Active (${minutesRemaining} min remaining)`;
              hideReloadButton();
          }
      }

      /**
       * Setup listener for session expiration events from the SDK
       */
      function setupSessionExpirationListener() {
          // Listen for session expiration events
          window.addEventListener('message', function(event) {
              // Only accept messages from our iframe server
              if (event.origin !== 'https://secure-iframe-container.stage.localpayment.com') {
                  return;
              }

              // Handle session expiration events
              if (event.data && event.data.source === 'lp-session' && event.data.type === 'SESSION_EXPIRED') {
                  handleSessionExpiration();
              }
          });
      }

      /**
       * Handle session expiration
       */
      function handleSessionExpiration() {
          isInitialized = false;
          paymentCard.classList.add('disabled');
          processBtn.disabled = true;
          sessionIndicator.className = 'session-indicator expired';
          sessionStatus.textContent = 'Session: Expired';
          
          showOutput('Session Expired', { 
              message: 'Your session has expired. Please restart the demo to continue.',
              timestamp: new Date().toISOString()
          }, 'warning');
          
          showReloadButton();
      }

      /**
       * Show the reload button
       */
      function showReloadButton() {
          reloadBtn.style.display = 'inline-flex';
      }

      /**
       * Hide the reload button
       */
      function hideReloadButton() {
          reloadBtn.style.display = 'none';
      }

      /**
       * Reload the page to restart the demo
       */
      function reloadDemo() {
          window.location.reload();
      }

      /**
       * Update initialization status message
       */
      function setInitStatus(message, type) {
          initStatus.textContent = message;
          initStatus.style.display = 'block';
          initStatus.className = 'init-status';
          
          if (type === 'success') {
              initStatus.classList.add('success');
          } else if (type === 'error') {
              initStatus.classList.add('error');
          } else if (type === 'warning') {
              initStatus.classList.add('warning');
          }
      }

      // =============================================================================
      // TEST CARD AUTO-FILL FUNCTIONS
      // =============================================================================

      /**
       * Generate a random 3-digit CVV
       */
      function generateRandomCVV() {
          return Math.floor(100 + Math.random() * 900).toString();
      }

      /**
       * Generate a future expiration date in MM-YYYY format
       */
      function generateFutureExpiry() {
          const now = new Date();
          const month = String(now.getMonth() + 1).padStart(2, '0');
          const year = now.getFullYear() + Math.floor(Math.random() * 3) + 1; // 1-3 years in future
          return `${month}-${year}`;
      }

      /**
       * Auto-fill test card data
       */
      function autoFillTestCard(cardNumberValue, cardType) {
          if (!isInitialized) {
              showOutput('SDK Not Initialized', { 
                  error: 'Please initialize the SDK first before using test cards',
              }, 'error');
              return;
          }

          try {
              // Generate test data
              const testData = {
                  cardNumber: cardNumberValue,
                  cvv: generateRandomCVV(),
                  expiry: generateFutureExpiry()
              };
              
              // Show the test data to the user
              showOutput('Test Card Data', {
                  message: `You can use the following ${cardType} test values:`,
                  card_number: testData.cardNumber,
                  cvv: testData.cvv,
                  expiry: testData.expiry,
                  instructions: 'Please manually enter these values in the corresponding fields'
              }, 'success');
              
          } catch (error) {
              console.error('Error with test card:', error);
              showOutput('Test Card Error', { 
                  error: 'Cannot display test card information.',
              }, 'error');
          }
      }

      // =============================================================================
      // FIELD EVENT HANDLING
      // =============================================================================

      /**
       * Setup postMessage listener for iframe validation updates
       */
      function setupMessageListener() {
          window.addEventListener('message', function(event) {
              // Only accept messages from our iframe server
              if (event.origin !== 'https://secure-iframe-container.stage.localpayment.com') {
                  return;
              }

              // Handle validation updates from iframes
              if (event.data && event.data.source === 'lp-iframe-validation' && event.data.type === 'FIELD_VALIDATION_UPDATE') {
                  const { fieldType, isValid, isTokenized, message } = event.data;
                  
                  // Map fieldType to our fieldsReady keys
                  const fieldMap = {
                      'pan': 'pan',
                      'cvv': 'cvv', 
                      'expiration': 'expiration'
                  };
                  
                  const fieldKey = fieldMap[fieldType];
                  if (fieldKey) {
                      // Update field ready status based on isValid
                      fieldsReady[fieldKey] = isValid;
                      
                      // Update payment button state
                      updatePaymentButton();
                      
                      // Update field status display
                      updateFieldStatusFromIframe(fieldType, isValid, message);
                  }
              }
          });
      }

      /**
       * Setup event listeners for all payment fields
       */
      function setupFieldListeners() {
          // Card number field events
          cardNumber.on('change', (state) => {
              updateFieldStatus('card-number', state);
          });

          cardNumber.on('tokenized', (token) => {
              fieldsReady.pan = true;
              updatePaymentButton();
          });

          cardNumber.on('error', (error) => {
              fieldsReady.pan = false;
              updatePaymentButton();
              const message = (error && (error.error || (error.metadata && error.metadata.reason))) || 'Tokenization failed';
              updateFieldStatus('card-number', { isValid: false, error: message, isEmpty: false });
              showOutput('Card Number Error', { error: message, metadata: error && error.metadata }, 'error');
              console.error('Card number error:', error);
          });

          // CVV field events
          cvv.on('change', (state) => {
              updateFieldStatus('cvv', state);
          });

          cvv.on('tokenized', (token) => {
              fieldsReady.cvv = true;
              updatePaymentButton();
          });

          cvv.on('error', (error) => {
              fieldsReady.cvv = false;
              updatePaymentButton();
              const message = (error && (error.error || (error.metadata && error.metadata.reason))) || 'Tokenization failed';
              updateFieldStatus('cvv', { isValid: false, error: message, isEmpty: false });
              showOutput('CVV Error', { error: message, metadata: error && error.metadata }, 'error');
              console.error('CVV error:', error);
          });

          // Expiry field events
          expiry.on('change', (state) => {
              updateFieldStatus('exp', state);
          });

          expiry.on('tokenized', (token) => {
              fieldsReady.expiration = true;
              updatePaymentButton();
          });

          expiry.on('error', (error) => {
              fieldsReady.expiration = false;
              updatePaymentButton();
              const message = (error && (error.error || (error.metadata && error.metadata.reason))) || 'Tokenization failed';
              updateFieldStatus('exp', { isValid: false, error: message, isEmpty: false });
              showOutput('Expiry Error', { error: message, metadata: error && error.metadata }, 'error');
              console.error('Expiry error:', error);
          });
      }

      /**
       * Update the visual status of a field based on its state
       */
      function updateFieldStatus(fieldName, state) {
          const statusElement = document.getElementById(`${fieldName}-status`);
          const indicator = statusElement.querySelector('.status-indicator');
          const text = statusElement.querySelector('.status-text');
          const container = document.getElementById(`${fieldName}-container`);

          // Update container styling
          container.classList.remove('error', 'valid');
          
          if (state.error) {
              container.classList.add('error');
              indicator.className = 'status-indicator error';
              text.textContent = state.error;
          } else if (state.isValid) {
              container.classList.add('valid');
              indicator.className = 'status-indicator valid';
              text.textContent = 'Valid';
          } else if (state.isEmpty) {
              indicator.className = 'status-indicator';
              text.textContent = getPlaceholderText(fieldName);
          } else {
              indicator.className = 'status-indicator';
              text.textContent = 'Incomplete';
          }
      }

      /**
       * Update field status based on messages from iframe
       */
      function updateFieldStatusFromIframe(fieldType, isValid, message) {
          // Map iframe fieldType to our field names
          const fieldMap = {
              'pan': 'card-number',
              'cvv': 'cvv',
              'expiration': 'exp'
          };
          
          const fieldName = fieldMap[fieldType];
          if (!fieldName) return;
          
          const statusElement = document.getElementById(`${fieldName}-status`);
          const indicator = statusElement.querySelector('.status-indicator');
          const text = statusElement.querySelector('.status-text');
          const container = document.getElementById(`${fieldName}-container`);

          // Update container styling
          container.classList.remove('error', 'valid');
          
          if (isValid) {
              container.classList.add('valid');
              indicator.className = 'status-indicator valid';
              text.textContent = 'Valid';
          } else {
              container.classList.add('error');
              indicator.className = 'session-indicator error';
              text.textContent = message || 'Invalid';
          }
      }

      /**
       * Get placeholder text for a field
       */
      function getPlaceholderText(fieldName) {
          const placeholders = {
              'card-number': 'Enter card number',
              'cvv': 'Enter CVV',
              'exp': 'Enter MM-YYYY'
          };
          return placeholders[fieldName] || 'Enter value';
      }

      /**
       * Update the payment button state based on field readiness
       */
      function updatePaymentButton() {
          const allReady = Object.values(fieldsReady).every(ready => ready);
          processBtn.disabled = !allReady || !isInitialized;
      }

      // =============================================================================
      // PAYMENT PROCESSING
      // =============================================================================

      /**
       * Process the payment by consolidating tokens
       */
      async function processPayment() {
          try {
              // Validate SDK is initialized
              if (!isInitialized) {
                  throw new Error('SDK is not initialized. Please configure and initialize first.');
              }
              
              setButtonLoading(processBtn, true);
              hideOutput();

              // Get cardholder name from input
              const cardholderName = cardholderNameInput.value.trim();
              if (!cardholderName) {
                  throw new Error('Cardholder name is required');
              }
              
              // Get session data from SDK
              const sessionData = localpayment.getSessionData();
              const { sessionId, accessToken, clientCode } = sessionData;
              
              if (!sessionId || !accessToken) {
                  throw new Error('No active session available. Please restart the demo.');
              }

              // Call backend to consolidate tokens
              const response = await fetch('https://tokenization.api.stage.localpayment.com/api/tokenization/consolidate', {
                  method: 'POST',
                  headers: {
                      'Authorization': `Bearer ${accessToken}`,
                      'Content-Type': 'application/json'
                  },
                  body: JSON.stringify({
                      clientCode: clientCode,
                      sessionId: sessionId,
                      CardholderName: cardholderName
                  })
              });

              if (!response.ok) {
                  // Check if it's a session expiration error
                  if (response.status === 401 || response.status === 403) {
                      handleSessionExpiration();
                      throw new Error('Session expired during payment processing');
                  }
                  
                  const errorData = await response.json().catch(() => ({}));
                  throw new Error(`Consolidation failed: ${response.status} ${response.statusText} - ${errorData.message || 'Unknown error'}`);
              }

              const consolidationResult = await response.json();

              showOutput('Payment Token Created', {
                  status: 'SUCCESS',
                  message: 'Card tokens consolidated successfully',
                  cardholder_name: cardholderName,
                  timestamp: new Date().toISOString(),
                  response: consolidationResult
              }, 'success');

              // After successful payment, show reload button
              showReloadButton();

          } catch (error) {
              showOutput('Payment Failed', { 
                  error: error.message,
                  status: error.status || 'UNKNOWN',
                  timestamp: new Date().toISOString()
              }, 'error');
          } finally {
              setButtonLoading(processBtn, false);
          }
      }

      // =============================================================================
      // UTILITY FUNCTIONS
      // =============================================================================

      /**
       * Set loading state for a button
       */
      function setButtonLoading(button, loading) {
          const text = button.querySelector('.button-text') || button;
          const spinner = button.querySelector('.spinner');

          if (loading) {
              button.classList.add('loading');
              button.disabled = true;
              if (!spinner) {
                  button.insertAdjacentHTML('afterbegin', '<div class="spinner"></div>');
              }
              text.textContent = loading === true ? 'Processing...' : loading;
          } else {
              button.classList.remove('loading');
              const spinnerElement = button.querySelector('.spinner');
              if (spinnerElement) {
                  spinnerElement.remove();
              }
              text.textContent = 'Process Payment';
              updatePaymentButton(); // Restore proper disabled state
          }
      }

      /**
       * Show output message with data
       */
      function showOutput(title, data, type = 'success') {
          outputTitle.textContent = title;
          outputData.textContent = JSON.stringify(data, null, 2);
          output.className = `output ${type}`;
          output.style.display = 'block';
          output.scrollIntoView({ behavior: 'smooth' });
      }

      /**
       * Hide the output section
       */
      function hideOutput() {
          output.style.display = 'none';
      }

      /**
       * Clear all payment fields
       */
      function clearFields() {
          if (cardNumber) cardNumber.clear();
          if (cvv) cvv.clear();
          if (expiry) expiry.clear();
          
          // Reset field states
          fieldsReady = { pan: false, cvv: false, expiration: false };
          updatePaymentButton();
          hideOutput();
          
          // Reset field status displays
          ['card-number', 'cvv', 'exp'].forEach(fieldName => {
              const container = document.getElementById(`${fieldName}-container`);
              const statusElement = document.getElementById(`${fieldName}-status`);
              const indicator = statusElement.querySelector('.status-indicator');
              const text = statusElement.querySelector('.status-text');
              
              container.classList.remove('error', 'valid');
              indicator.className = 'status-indicator';
              text.textContent = getPlaceholderText(fieldName);
          });
      }

      // =============================================================================
      // EVENT LISTENERS
      // =============================================================================

      // Initialize button click handler
      if (initBtn) {
          initBtn.addEventListener('click', initializePayment);
      }
      
      // Process payment button click handler
      if (processBtn) {
          processBtn.addEventListener('click', processPayment);
      }
      
      // Clear fields button click handler
      if (clearBtn) {
          clearBtn.addEventListener('click', clearFields);
      }

      // Reload demo button click handler
      if (reloadBtn) {
          reloadBtn.addEventListener('click', reloadDemo);
      }

      // Test card click handlers
      if (visaTestCard) {
          visaTestCard.addEventListener('click', () => {
              autoFillTestCard('4111111111111111', 'Visa');
          });
      }

      if (mastercardTestCard) {
          mastercardTestCard.addEventListener('click', () => {
              autoFillTestCard('5555555555554444', 'Mastercard');
          });
      }

      // =============================================================================
      // APPLICATION STARTUP
      // =============================================================================

      // Initialize when DOM is ready
      document.addEventListener('DOMContentLoaded', function() {
          // Setup message listener for iframe communication
          setupMessageListener();
          
          console.log('Smart Fields SDK Demo loaded successfully');
      });

      // Global error handling
      window.addEventListener('error', (event) => {
          console.error('Global error:', event.error);
      });

      window.addEventListener('unhandledrejection', (event) => {
          console.error('Unhandled promise rejection:', event.reason);
      });
  </script>
</body>
</html>