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
Smart Fields SDK Demo

Smart Fields SDK

Secure payment tokenization demo

    <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>
```