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.
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?