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:
| Brand | Card Number | Description |
|---|---|---|
| Visa | 4111 1111 1111 1111 | Always succeeds |
| Mastercard | 5555 5555 5555 4444 | Always succeeds |
| Invalid Card | 4242 4242 4242 4241 | Fails validation |
| Declined | 4000 0000 0000 0127 | Transaction 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
changeevent withisValid: 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. Addlocalhostas an allowed domain for the Smart Fields API Key to avoid domain validation errors.
Example Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart Fields SDK Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f8fafc;
color: #334155;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
padding: 40px 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 700;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.card {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
position: relative;
transition: opacity 0.3s ease;
}
.card.disabled {
opacity: 0.6;
pointer-events: none;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151;
}
.field-container {
border: 2px solid #e5e7eb;
border-radius: 8px;
transition: border-color 0.2s;
background: white;
min-height: 46px;
}
.field-container:focus-within {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.field-container.error {
border-color: #ef4444;
}
.field-container.valid {
border-color: #10b981;
}
.field-row {
display: flex;
gap: 20px;
}
.field-row .form-group {
flex: 1;
}
.button {
background: #667eea;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.button:hover:not(:disabled) {
background: #5b6fd8;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.button:disabled {
background: #9ca3af;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.button.secondary {
background: #6b7280;
}
.button.secondary:hover:not(:disabled) {
background: #5b6471;
}
.button.danger {
background: #ef4444;
}
.button.danger:hover:not(:disabled) {
background: #dc2626;
}
.button-group {
display: flex;
gap: 12px;
margin-top: 30px;
flex-wrap: wrap;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #d1d5db;
display: inline-block;
margin-right: 8px;
}
.status-indicator.valid {
background: #10b981;
}
.status-indicator.error {
background: #ef4444;
}
.status-indicator.warning {
background: #f59e0b;
}
.field-status {
display: flex;
align-items: center;
font-size: 14px;
margin-top: 8px;
opacity: 0.8;
}
.output {
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
display: none;
}
.output.success {
background: #f0fdf4;
border-color: #bbf7d0;
color: #166534;
}
.output.error {
background: #fef2f2;
border-color: #fecaca;
color: #991b1b;
}
.output.warning {
background: #fffbeb;
border-color: #fed7aa;
color: #92400e;
}
.output h3 {
margin-bottom: 15px;
font-size: 18px;
}
.output-data {
font-family: 'Monaco', 'Consolas', monospace;
font-size: 14px;
background: rgba(0, 0, 0, 0.05);
padding: 15px;
border-radius: 6px;
white-space: pre-wrap;
word-break: break-all;
}
.loading {
opacity: 0.7;
pointer-events: none;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.demo-info {
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
}
.demo-info h3 {
color: #1e40af;
margin-bottom: 10px;
}
.test-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.test-card {
background: white;
padding: 15px;
border-radius: 6px;
border: 1px solid #d1d5db;
cursor: pointer;
transition: all 0.2s;
}
.test-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border-color: #667eea;
}
.test-card strong {
display: block;
margin-bottom: 5px;
color: #374151;
}
.test-card code {
font-size: 12px;
color: #6b7280;
}
.config-section {
background: #fefce8;
border: 1px solid #fef08a;
border-radius: 8px;
padding: 20px;
margin-bottom: 30px;
}
.config-section h3 {
color: #ca8a04;
margin-bottom: 15px;
}
.config-input {
width: 100%;
padding: 10px;
border: 1px solid #e5e7eb;
border-radius: 6px;
margin-bottom: 10px;
}
.init-status {
padding: 10px;
border-radius: 6px;
margin-top: 10px;
display: none;
}
.init-status.success {
background: #f0fdf4;
border: 1px solid #bbf7d0;
color: #166534;
}
.init-status.error {
background: #fef2f2;
border: 1px solid #fecaca;
color: #991b1b;
}
.init-status.warning {
background: #fffbeb;
border: 1px solid #fed7aa;
color: #92400e;
}
.session-info {
position: absolute;
top: 15px;
right: 15px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
background: #f1f5f9;
padding: 6px 12px;
border-radius: 20px;
}
.session-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: #d1d5db;
}
.session-indicator.active {
background: #10b981;
animation: pulse 2s infinite;
}
.session-indicator.expiring {
background: #f59e0b;
}
.session-indicator.expired {
background: #ef4444;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
@media (max-width: 600px) {
.field-row {
flex-direction: column;
gap: 0;
}
.button-group {
flex-direction: column;
}
.session-info {
position: relative;
top: 0;
right: 0;
margin-bottom: 15px;
justify-content: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Smart Fields SDK</h1>
<p>Secure payment tokenization demo</p>
</div>
<div class="config-section">
<h3>⚙️ Configuration</h3>
<div class="form-group">
<label class="form-label" for="client-code">Client Code</label>
<input type="text" id="client-code" class="config-input" placeholder="Enter your client code" value="your_client_code_here">
</div>
<div class="form-group">
<label class="form-label" for="api-key">API Key</label>
<input type="text" id="api-key" class="config-input" placeholder="Enter your API key" value="your_api_key_here">
</div>
<button id="init-btn" class="button">Initialize SDK</button>
<div id="init-status" class="init-status"></div>
</div>
<div class="demo-info">
<h3>🔒 Security Features</h3>
<p>This demo showcases Localpayment's secure tokenization system. Card data is collected in isolated iframes and never touches your server, ensuring PCI DSS compliance.</p>
<div class="test-cards">
<div class="test-card" id="visa-test-card">
<strong>Visa</strong>
<code>4111 1111 1111 1111</code>
</div>
<div class="test-card" id="mastercard-test-card">
<strong>Mastercard</strong>
<code>5555 5555 5555 4444</code>
</div>
</div>
</div>
<div id="payment-card" class="card disabled">
<div class="session-info">
<span class="session-indicator" id="session-indicator"></span>
<span id="session-status">Session: Not initialized</span>
</div>
<h2>Payment Information</h2>
<form id="payment-form">
<div class="form-group">
<label class="form-label" for="cardholder-name">Cardholder Name</label>
<input type="text" id="cardholder-name" class="config-input" placeholder="Enter cardholder name" value="John Doe">
</div>
<div class="form-group">
<label class="form-label" for="card-number">Card Number</label>
<div id="card-number-container" class="field-container"></div>
<div id="card-number-status" class="field-status">
<span class="status-indicator"></span>
<span class="status-text">Enter card number</span>
</div>
</div>
<div class="field-row">
<div class="form-group">
<label class="form-label" for="cvv">CVV</label>
<div id="cvv-container" class="field-container"></div>
<div id="cvv-status" class="field-status">
<span class="status-indicator"></span>
<span class="status-text">Enter CVV</span>
</div>
</div>
<div class="form-group">
<label class="form-label" for="expiry">Expiry Date</label>
<div id="exp-container" class="field-container"></div>
<div id="exp-status" class="field-status">
<span class="status-indicator"></span>
<span class="status-text">Enter MM-YYYY</span>
</div>
</div>
</div>
<div class="button-group">
<button type="button" id="process-btn" class="button" disabled>
<span class="button-text">Process Payment</span>
</button>
<button type="button" id="clear-btn" class="button secondary">
Clear Fields
</button>
<button type="button" id="reload-btn" class="button danger" style="display: none;">
Restart Demo
</button>
</div>
</form>
<div id="output" class="output">
<h3 id="output-title">Result</h3>
<div id="output-data" class="output-data"></div>
</div>
</div>
</div>
<!-- Load Smart Fields SDK -->
<script src="https://sdk.stage.localpayment.com/localpayment-sdk.min.js"></script>
<script>
'use strict';
// =============================================================================
// CONFIGURATION
// =============================================================================
// Application state
let localpayment = null;
let cardNumber = null;
let cvv = null;
let expiry = null;
let fieldsReady = { pan: false, cvv: false, expiration: false };
let isInitialized = false;
let sessionExpiryTimer = null;
let sessionExpiryTime = null;
// DOM elements
const initBtn = document.getElementById('init-btn');
const initStatus = document.getElementById('init-status');
const clientCodeInput = document.getElementById('client-code');
const apiKeyInput = document.getElementById('api-key');
const processBtn = document.getElementById('process-btn');
const clearBtn = document.getElementById('clear-btn');
const reloadBtn = document.getElementById('reload-btn');
const output = document.getElementById('output');
const outputTitle = document.getElementById('output-title');
const outputData = document.getElementById('output-data');
const cardholderNameInput = document.getElementById('cardholder-name');
const paymentCard = document.getElementById('payment-card');
const sessionIndicator = document.getElementById('session-indicator');
const sessionStatus = document.getElementById('session-status');
const visaTestCard = document.getElementById('visa-test-card');
const mastercardTestCard = document.getElementById('mastercard-test-card');
// =============================================================================
// INITIALIZATION FUNCTIONS
// =============================================================================
/**
* Initialize the Smart Fields SDK with provided credentials
*/
async function initializePayment() {
try {
// Get configuration values
const clientCode = clientCodeInput.value.trim();
const apiKey = apiKeyInput.value.trim();
// Validate configuration
if (!clientCode || !apiKey) {
throw new Error('Client code and API key are required');
}
setInitStatus('Initializing SDK...', '');
// Disable initialization button during process
initBtn.disabled = true;
// Initialize Smart Fields SDK
localpayment = LP({
clientCode: clientCode,
apiKey: apiKey
});
// Create session with backend
setInitStatus('Creating session...', '');
await localpayment.createSession();
// Create secure fields AFTER session is ready
setInitStatus('Creating secure fields...', '');
cardNumber = localpayment.create('pan');
cvv = localpayment.create('cvv');
expiry = localpayment.create('expiration');
// Mount fields to containers
cardNumber.mount('#card-number-container');
cvv.mount('#cvv-container');
expiry.mount('#exp-container');
// Setup field event listeners
setupFieldListeners();
// Update UI state
isInitialized = true;
setInitStatus('SDK initialized successfully!', 'success');
paymentCard.classList.remove('disabled');
// Update session status display
updateSessionStatusDisplay();
// Start session expiry monitoring
startSessionExpiryMonitoring();
// Setup session expiration listener
setupSessionExpirationListener();
} catch (error) {
console.error('Failed to initialize Smart Fields SDK:', error);
setInitStatus(`Initialization failed: ${error.message}`, 'error');
isInitialized = false;
initBtn.disabled = false;
}
}
/**
* Update the session status display
*/
function updateSessionStatusDisplay() {
if (!localpayment) return;
const sessionData = localpayment.getSessionData();
if (sessionData && sessionData.expiresAt) {
sessionExpiryTime = new Date(sessionData.expiresAt);
updateSessionStatus();
} else {
sessionIndicator.className = 'session-indicator active';
sessionStatus.textContent = 'Session: Active';
}
}
/**
* Start monitoring session expiry time
*/
function startSessionExpiryMonitoring() {
// Clear any existing timer
if (sessionExpiryTimer) {
clearInterval(sessionExpiryTimer);
}
// Get session data to determine expiry
const sessionData = localpayment.getSessionData();
if (sessionData && sessionData.expiresAt) {
sessionExpiryTime = new Date(sessionData.expiresAt);
updateSessionStatus();
// Update session status every minute
sessionExpiryTimer = setInterval(updateSessionStatus, 60000);
}
}
/**
* Update the session status indicator
*/
function updateSessionStatus() {
if (!sessionExpiryTime) return;
const now = new Date();
const timeRemaining = sessionExpiryTime - now;
const minutesRemaining = Math.floor(timeRemaining / 60000);
if (minutesRemaining <= 0) {
// Session has expired
sessionIndicator.className = 'session-indicator expired';
sessionStatus.textContent = 'Session: Expired';
handleSessionExpiration();
} else if (minutesRemaining <= 5) {
// Session expiring soon
sessionIndicator.className = 'session-indicator expiring';
sessionStatus.textContent = `Session: Expiring in ${minutesRemaining} min`;
showReloadButton();
} else {
// Session is active
sessionIndicator.className = 'session-indicator active';
sessionStatus.textContent = `Session: Active (${minutesRemaining} min remaining)`;
hideReloadButton();
}
}
/**
* Setup listener for session expiration events from the SDK
*/
function setupSessionExpirationListener() {
// Listen for session expiration events
window.addEventListener('message', function(event) {
// Only accept messages from our iframe server
if (event.origin !== 'https://secure-iframe-container.stage.localpayment.com') {
return;
}
// Handle session expiration events
if (event.data && event.data.source === 'lp-session' && event.data.type === 'SESSION_EXPIRED') {
handleSessionExpiration();
}
});
}
/**
* Handle session expiration
*/
function handleSessionExpiration() {
isInitialized = false;
paymentCard.classList.add('disabled');
processBtn.disabled = true;
sessionIndicator.className = 'session-indicator expired';
sessionStatus.textContent = 'Session: Expired';
showOutput('Session Expired', {
message: 'Your session has expired. Please restart the demo to continue.',
timestamp: new Date().toISOString()
}, 'warning');
showReloadButton();
}
/**
* Show the reload button
*/
function showReloadButton() {
reloadBtn.style.display = 'inline-flex';
}
/**
* Hide the reload button
*/
function hideReloadButton() {
reloadBtn.style.display = 'none';
}
/**
* Reload the page to restart the demo
*/
function reloadDemo() {
window.location.reload();
}
/**
* Update initialization status message
*/
function setInitStatus(message, type) {
initStatus.textContent = message;
initStatus.style.display = 'block';
initStatus.className = 'init-status';
if (type === 'success') {
initStatus.classList.add('success');
} else if (type === 'error') {
initStatus.classList.add('error');
} else if (type === 'warning') {
initStatus.classList.add('warning');
}
}
// =============================================================================
// TEST CARD AUTO-FILL FUNCTIONS
// =============================================================================
/**
* Generate a random 3-digit CVV
*/
function generateRandomCVV() {
return Math.floor(100 + Math.random() * 900).toString();
}
/**
* Generate a future expiration date in MM-YYYY format
*/
function generateFutureExpiry() {
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, '0');
const year = now.getFullYear() + Math.floor(Math.random() * 3) + 1; // 1-3 years in future
return `${month}-${year}`;
}
/**
* Auto-fill test card data
*/
function autoFillTestCard(cardNumberValue, cardType) {
if (!isInitialized) {
showOutput('SDK Not Initialized', {
error: 'Please initialize the SDK first before using test cards',
}, 'error');
return;
}
try {
// Generate test data
const testData = {
cardNumber: cardNumberValue,
cvv: generateRandomCVV(),
expiry: generateFutureExpiry()
};
// Show the test data to the user
showOutput('Test Card Data', {
message: `You can use the following ${cardType} test values:`,
card_number: testData.cardNumber,
cvv: testData.cvv,
expiry: testData.expiry,
instructions: 'Please manually enter these values in the corresponding fields'
}, 'success');
} catch (error) {
console.error('Error with test card:', error);
showOutput('Test Card Error', {
error: 'Cannot display test card information.',
}, 'error');
}
}
// =============================================================================
// FIELD EVENT HANDLING
// =============================================================================
/**
* Setup postMessage listener for iframe validation updates
*/
function setupMessageListener() {
window.addEventListener('message', function(event) {
// Only accept messages from our iframe server
if (event.origin !== 'https://secure-iframe-container.stage.localpayment.com') {
return;
}
// Handle validation updates from iframes
if (event.data && event.data.source === 'lp-iframe-validation' && event.data.type === 'FIELD_VALIDATION_UPDATE') {
const { fieldType, isValid, isTokenized, message } = event.data;
// Map fieldType to our fieldsReady keys
const fieldMap = {
'pan': 'pan',
'cvv': 'cvv',
'expiration': 'expiration'
};
const fieldKey = fieldMap[fieldType];
if (fieldKey) {
// Update field ready status based on isValid
fieldsReady[fieldKey] = isValid;
// Update payment button state
updatePaymentButton();
// Update field status display
updateFieldStatusFromIframe(fieldType, isValid, message);
}
}
});
}
/**
* Setup event listeners for all payment fields
*/
function setupFieldListeners() {
// Card number field events
cardNumber.on('change', (state) => {
updateFieldStatus('card-number', state);
});
cardNumber.on('tokenized', (token) => {
fieldsReady.pan = true;
updatePaymentButton();
});
cardNumber.on('error', (error) => {
fieldsReady.pan = false;
updatePaymentButton();
const message = (error && (error.error || (error.metadata && error.metadata.reason))) || 'Tokenization failed';
updateFieldStatus('card-number', { isValid: false, error: message, isEmpty: false });
showOutput('Card Number Error', { error: message, metadata: error && error.metadata }, 'error');
console.error('Card number error:', error);
});
// CVV field events
cvv.on('change', (state) => {
updateFieldStatus('cvv', state);
});
cvv.on('tokenized', (token) => {
fieldsReady.cvv = true;
updatePaymentButton();
});
cvv.on('error', (error) => {
fieldsReady.cvv = false;
updatePaymentButton();
const message = (error && (error.error || (error.metadata && error.metadata.reason))) || 'Tokenization failed';
updateFieldStatus('cvv', { isValid: false, error: message, isEmpty: false });
showOutput('CVV Error', { error: message, metadata: error && error.metadata }, 'error');
console.error('CVV error:', error);
});
// Expiry field events
expiry.on('change', (state) => {
updateFieldStatus('exp', state);
});
expiry.on('tokenized', (token) => {
fieldsReady.expiration = true;
updatePaymentButton();
});
expiry.on('error', (error) => {
fieldsReady.expiration = false;
updatePaymentButton();
const message = (error && (error.error || (error.metadata && error.metadata.reason))) || 'Tokenization failed';
updateFieldStatus('exp', { isValid: false, error: message, isEmpty: false });
showOutput('Expiry Error', { error: message, metadata: error && error.metadata }, 'error');
console.error('Expiry error:', error);
});
}
/**
* Update the visual status of a field based on its state
*/
function updateFieldStatus(fieldName, state) {
const statusElement = document.getElementById(`${fieldName}-status`);
const indicator = statusElement.querySelector('.status-indicator');
const text = statusElement.querySelector('.status-text');
const container = document.getElementById(`${fieldName}-container`);
// Update container styling
container.classList.remove('error', 'valid');
if (state.error) {
container.classList.add('error');
indicator.className = 'status-indicator error';
text.textContent = state.error;
} else if (state.isValid) {
container.classList.add('valid');
indicator.className = 'status-indicator valid';
text.textContent = 'Valid';
} else if (state.isEmpty) {
indicator.className = 'status-indicator';
text.textContent = getPlaceholderText(fieldName);
} else {
indicator.className = 'status-indicator';
text.textContent = 'Incomplete';
}
}
/**
* Update field status based on messages from iframe
*/
function updateFieldStatusFromIframe(fieldType, isValid, message) {
// Map iframe fieldType to our field names
const fieldMap = {
'pan': 'card-number',
'cvv': 'cvv',
'expiration': 'exp'
};
const fieldName = fieldMap[fieldType];
if (!fieldName) return;
const statusElement = document.getElementById(`${fieldName}-status`);
const indicator = statusElement.querySelector('.status-indicator');
const text = statusElement.querySelector('.status-text');
const container = document.getElementById(`${fieldName}-container`);
// Update container styling
container.classList.remove('error', 'valid');
if (isValid) {
container.classList.add('valid');
indicator.className = 'status-indicator valid';
text.textContent = 'Valid';
} else {
container.classList.add('error');
indicator.className = 'session-indicator error';
text.textContent = message || 'Invalid';
}
}
/**
* Get placeholder text for a field
*/
function getPlaceholderText(fieldName) {
const placeholders = {
'card-number': 'Enter card number',
'cvv': 'Enter CVV',
'exp': 'Enter MM-YYYY'
};
return placeholders[fieldName] || 'Enter value';
}
/**
* Update the payment button state based on field readiness
*/
function updatePaymentButton() {
const allReady = Object.values(fieldsReady).every(ready => ready);
processBtn.disabled = !allReady || !isInitialized;
}
// =============================================================================
// PAYMENT PROCESSING
// =============================================================================
/**
* Process the payment by consolidating tokens
*/
async function processPayment() {
try {
// Validate SDK is initialized
if (!isInitialized) {
throw new Error('SDK is not initialized. Please configure and initialize first.');
}
setButtonLoading(processBtn, true);
hideOutput();
// Get cardholder name from input
const cardholderName = cardholderNameInput.value.trim();
if (!cardholderName) {
throw new Error('Cardholder name is required');
}
// Get session data from SDK
const sessionData = localpayment.getSessionData();
const { sessionId, accessToken, clientCode } = sessionData;
if (!sessionId || !accessToken) {
throw new Error('No active session available. Please restart the demo.');
}
// Call backend to consolidate tokens
const response = await fetch('https://tokenization.api.stage.localpayment.com/api/tokenization/consolidate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
clientCode: clientCode,
sessionId: sessionId,
CardholderName: cardholderName
})
});
if (!response.ok) {
// Check if it's a session expiration error
if (response.status === 401 || response.status === 403) {
handleSessionExpiration();
throw new Error('Session expired during payment processing');
}
const errorData = await response.json().catch(() => ({}));
throw new Error(`Consolidation failed: ${response.status} ${response.statusText} - ${errorData.message || 'Unknown error'}`);
}
const consolidationResult = await response.json();
showOutput('Payment Token Created', {
status: 'SUCCESS',
message: 'Card tokens consolidated successfully',
cardholder_name: cardholderName,
timestamp: new Date().toISOString(),
response: consolidationResult
}, 'success');
// After successful payment, show reload button
showReloadButton();
} catch (error) {
showOutput('Payment Failed', {
error: error.message,
status: error.status || 'UNKNOWN',
timestamp: new Date().toISOString()
}, 'error');
} finally {
setButtonLoading(processBtn, false);
}
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
/**
* Set loading state for a button
*/
function setButtonLoading(button, loading) {
const text = button.querySelector('.button-text') || button;
const spinner = button.querySelector('.spinner');
if (loading) {
button.classList.add('loading');
button.disabled = true;
if (!spinner) {
button.insertAdjacentHTML('afterbegin', '<div class="spinner"></div>');
}
text.textContent = loading === true ? 'Processing...' : loading;
} else {
button.classList.remove('loading');
const spinnerElement = button.querySelector('.spinner');
if (spinnerElement) {
spinnerElement.remove();
}
text.textContent = 'Process Payment';
updatePaymentButton(); // Restore proper disabled state
}
}
/**
* Show output message with data
*/
function showOutput(title, data, type = 'success') {
outputTitle.textContent = title;
outputData.textContent = JSON.stringify(data, null, 2);
output.className = `output ${type}`;
output.style.display = 'block';
output.scrollIntoView({ behavior: 'smooth' });
}
/**
* Hide the output section
*/
function hideOutput() {
output.style.display = 'none';
}
/**
* Clear all payment fields
*/
function clearFields() {
if (cardNumber) cardNumber.clear();
if (cvv) cvv.clear();
if (expiry) expiry.clear();
// Reset field states
fieldsReady = { pan: false, cvv: false, expiration: false };
updatePaymentButton();
hideOutput();
// Reset field status displays
['card-number', 'cvv', 'exp'].forEach(fieldName => {
const container = document.getElementById(`${fieldName}-container`);
const statusElement = document.getElementById(`${fieldName}-status`);
const indicator = statusElement.querySelector('.status-indicator');
const text = statusElement.querySelector('.status-text');
container.classList.remove('error', 'valid');
indicator.className = 'status-indicator';
text.textContent = getPlaceholderText(fieldName);
});
}
// =============================================================================
// EVENT LISTENERS
// =============================================================================
// Initialize button click handler
if (initBtn) {
initBtn.addEventListener('click', initializePayment);
}
// Process payment button click handler
if (processBtn) {
processBtn.addEventListener('click', processPayment);
}
// Clear fields button click handler
if (clearBtn) {
clearBtn.addEventListener('click', clearFields);
}
// Reload demo button click handler
if (reloadBtn) {
reloadBtn.addEventListener('click', reloadDemo);
}
// Test card click handlers
if (visaTestCard) {
visaTestCard.addEventListener('click', () => {
autoFillTestCard('4111111111111111', 'Visa');
});
}
if (mastercardTestCard) {
mastercardTestCard.addEventListener('click', () => {
autoFillTestCard('5555555555554444', 'Mastercard');
});
}
// =============================================================================
// APPLICATION STARTUP
// =============================================================================
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
// Setup message listener for iframe communication
setupMessageListener();
console.log('Smart Fields SDK Demo loaded successfully');
});
// Global error handling
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
});
</script>
</body>
</html>Updated 3 days ago
