Smart Fields Testing & Demos

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>