Testing & Demos

Testing your Smart Fields integration is a critical step before moving into production. With the Localpayment Sandbox, you can safely simulate real-world scenarios, validate your frontend and backend flows, and ensure your implementation is secure and stable.

Stage Environment

To test your integration, use the sandbox environment with test API keys. Configure your SDK initialization as follows:

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

In your application, make sure you are using both the correct API Key and the Stage SDK URL:

<script src="https://sdk.stage.localpayment.com/localpayment-sdk.min.js"></script>

Notes:

  • You must generate separate API Keys for each environment (Stage and Production).

  • Domains are validated at the moment a session is initialized. If the request comes from a non-registered domain, the SDK will return a “Invalid Source Domain” error.

  • Sandbox transactions do not move real money, but they simulate the full payment flow so you can test securely.

Test Card Numbers

You can use the following test card numbers to simulate different scenarios.

Brand
Card Number
Notes

Visa

4111 1111 1111 1111

Always succeeds

Mastercard

5555 5555 55554444

Always succeeds

Invalid Card

4242424242424241

Invalid card number

Declined

4000000000000127

Declined

Notes:

  • Use any valid future date for the expiration (e.g., 12/2028).

  • Use any valid 3-digit CVV (e.g., 123).

  • Use any name for the cardholder.

Error Simulation

Test your error handling by simulating these common scenarios:

Invalid Card Number

// Use: 4242424242424241 (fails Luhn validation)
// Expected: 'change' event with isValid: false

Expired Card

// Use past expiration date: 12/2020
// Expected: Tokenization failure with expiration error

Session Expiration

// Wait 10+ minutes after initializeSession()
// Expected: Tokenization fails with session expiration error

Network Errors

// Temporarily disable internet connection
// Expected: Tokenization fails with network error

Your integration should listen to error events from the SDK and handle them gracefully (e.g., show error messages to the user, retry).

Complete Test Example

Here's a complete example page you can use to validate your integration:

See full code example

Note: To run the example, serve the site from localhost (e.g., via a local web server). Ensure localhost is added as an allowed domain on the Smart Fields API Key you’re using; otherwise, session initialization will fail due to domain validation.

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

Last updated

Was this helpful?