Learn how to implement robust error handling with the Paysight Widget
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paysight Widget - Error Handling</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.5;
margin: 0;
padding: 20px;
background-color: #f8fafc;
}
.container {
max-width: 600px;
margin: 40px auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
padding: 24px;
}
.header {
text-align: center;
margin-bottom: 24px;
}
.header h1 {
margin: 0;
color: #0f172a;
font-size: 24px;
}
.header p {
margin: 8px 0 0;
color: #64748b;
}
.widget-container {
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}
.error-container {
margin-bottom: 20px;
}
.error-message {
background-color: #fee2e2;
border: 1px solid #ef4444;
color: #b91c1c;
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 12px;
font-size: 14px;
display: none;
}
.error-details {
background-color: #fff1f2;
border: 1px solid #fecdd3;
color: #881337;
padding: 12px 16px;
border-radius: 6px;
font-size: 13px;
font-family: monospace;
white-space: pre-wrap;
display: none;
margin-top: 8px;
}
.error-actions {
display: none;
margin-top: 12px;
}
.error-action-button {
background-color: #ef4444;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.error-action-button:hover {
background-color: #dc2626;
}
.error-action-button + .error-action-button {
margin-left: 8px;
background-color: #6b7280;
}
.error-action-button + .error-action-button:hover {
background-color: #4b5563;
}
.debug-panel {
margin-top: 24px;
padding: 16px;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
}
.debug-panel h3 {
margin: 0 0 12px;
color: #0f172a;
font-size: 16px;
}
.debug-log {
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 4px;
padding: 12px;
max-height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 13px;
line-height: 1.4;
}
.debug-entry {
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #f1f5f9;
}
.debug-entry:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.debug-timestamp {
color: #64748b;
font-size: 12px;
}
.debug-type {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
margin: 0 8px;
}
.debug-type.error {
background-color: #fee2e2;
color: #b91c1c;
}
.debug-type.warning {
background-color: #fef3c7;
color: #92400e;
}
.debug-type.info {
background-color: #e0f2fe;
color: #0369a1;
}
.debug-message {
color: #0f172a;
}
.retry-button {
display: none;
margin-top: 16px;
background-color: #3b82f6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
}
.retry-button:hover {
background-color: #2563eb;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Secure Payment</h1>
<p>Complete your payment with error handling demonstration</p>
</div>
<div class="error-container">
<div id="error-message" class="error-message"></div>
<div id="error-details" class="error-details"></div>
<div id="error-actions" class="error-actions">
<button class="error-action-button" onclick="retryPayment()">
Retry Payment
</button>
<button class="error-action-button" onclick="resetWidget()">
Reset Form
</button>
</div>
</div>
<div id="widget-container" class="widget-container"></div>
<button id="retry-button" class="retry-button" onclick="retryPayment()">
Try Again
</button>
<div class="debug-panel">
<h3>Debug Log</h3>
<div id="debug-log" class="debug-log"></div>
</div>
</div>
<!-- Add the Paysight Widget SDK -->
<script src="https://payment.paysight.io/widget-sdk.js"></script>
<script>
// Error types and messages
const ErrorTypes = {
VALIDATION: 'VALIDATION_ERROR',
NETWORK: 'NETWORK_ERROR',
PAYMENT: 'PAYMENT_ERROR',
WIDGET: 'ERROR',
SYSTEM: 'SYSTEM_ERROR'
};
const ErrorMessages = {
[ErrorTypes.VALIDATION]: {
title: 'Invalid Input',
message: 'Please check your input and try again.',
recoverable: true
},
[ErrorTypes.NETWORK]: {
title: 'Connection Error',
message: 'Unable to connect to the payment service.',
recoverable: true
},
[ErrorTypes.PAYMENT]: {
title: 'Payment Failed',
message: 'Unable to process your payment.',
recoverable: true
},
[ErrorTypes.WIDGET]: {
title: 'Widget Error',
message: 'The payment widget encountered an error.',
recoverable: false
},
[ErrorTypes.SYSTEM]: {
title: 'System Error',
message: 'An unexpected error occurred.',
recoverable: false
}
};
// Widget configuration
const config = {
productId: YOUR_PRODUCT_ID,
sessionId: \`session_\${Date.now()}\`,
amount: 2999,
environment: 'production',
threeDSRequired: true,
ecom: true,
currency: 'USD',
locale: 'en-US'
};
// Widget instance
let widget;
let retryCount = 0;
const MAX_RETRIES = 3;
const RETRY_DELAY = 2000;
// Initialize widget
function initializeWidget() {
try {
widget = PaysightSDK.createWidget({
targetId: 'widget-container',
config,
onReady: handleWidgetReady,
onError: handleError,
onMessage: handleWidgetMessage
});
logDebug('Widget initialized', 'info');
} catch (error) {
handleError({
type: ErrorTypes.SYSTEM,
message: 'Failed to initialize widget',
details: error
});
}
}
// Widget ready handler
function handleWidgetReady() {
logDebug('Widget ready', 'info');
hideError();
hideRetryButton();
}
// Error handler
function handleError(error) {
logDebug(\`Error: \${error.message}\`, 'error');
const errorType = getErrorType(error);
const errorInfo = ErrorMessages[errorType];
showError(
errorInfo.title,
error.message || errorInfo.message,
error.details,
errorInfo.recoverable
);
if (errorInfo.recoverable && retryCount < MAX_RETRIES) {
showRetryButton();
}
}
// Message handler for widget events
function handleWidgetMessage(message) {
switch (message.type) {
case 'VALIDATION_ERROR':
handleValidationError(message.payload);
break;
case 'PAYMENT_ERROR':
handlePaymentError(message.payload);
break;
case 'NETWORK_ERROR':
handleNetworkError(message.payload);
break;
case 'PAYMENT_3DS_ERROR':
handle3DSError(message.payload);
break;
case 'PAYMENT_SUCCESS':
handlePaymentSuccess(message.payload);
break;
}
}
// Error type handlers
function handlePaymentError(payload) {
handleError({
type: ErrorTypes.PAYMENT,
message: payload.message,
details: payload
});
}
function handleValidationError(payload) {
handleError({
type: ErrorTypes.VALIDATION,
message: 'Please check your card details',
details: payload
});
}
function handleNetworkError(payload) {
handleError({
type: ErrorTypes.NETWORK,
message: 'Connection failed. Please check your internet connection.',
details: payload
});
}
function handle3DSError(payload) {
handleError({
type: ErrorTypes.SYSTEM,
message: '3D Secure Error',
details: payload
});
}
function handlePaymentSuccess(payload) {
handleError({
type: ErrorTypes.SYSTEM,
message: 'Payment Successful',
details: payload
});
}
// Helper functions
function getErrorType(error) {
if (error.type) return error.type;
if (error.code?.includes('VALIDATION')) return ErrorTypes.VALIDATION;
if (error.code?.includes('NETWORK')) return ErrorTypes.NETWORK;
if (error.code?.includes('PAYMENT')) return ErrorTypes.PAYMENT;
if (error.code?.includes('WIDGET')) return ErrorTypes.WIDGET;
return ErrorTypes.SYSTEM;
}
function showError(title, message, details, recoverable) {
const errorMessage = document.getElementById('error-message');
const errorDetails = document.getElementById('error-details');
const errorActions = document.getElementById('error-actions');
errorMessage.textContent = \`\${title}: \${message}\`;
errorMessage.style.display = 'block';
if (details) {
errorDetails.textContent = JSON.stringify(details, null, 2);
errorDetails.style.display = 'block';
}
if (recoverable) {
errorActions.style.display = 'block';
}
}
function hideError() {
document.getElementById('error-message').style.display = 'none';
document.getElementById('error-details').style.display = 'none';
document.getElementById('error-actions').style.display = 'none';
}
function showRetryButton() {
document.getElementById('retry-button').style.display = 'block';
}
function hideRetryButton() {
document.getElementById('retry-button').style.display = 'none';
}
// Retry mechanism
function retryPayment() {
if (retryCount >= MAX_RETRIES) {
handleError({
type: ErrorTypes.SYSTEM,
message: 'Maximum retry attempts reached',
recoverable: false
});
return;
}
retryCount++;
logDebug(\`Retrying payment (attempt \${retryCount})\`, 'info');
setTimeout(() => {
resetWidget();
initializeWidget();
}, RETRY_DELAY);
}
function resetWidget() {
if (widget) {
widget.destroy();
}
hideError();
hideRetryButton();
initializeWidget();
retryCount = 0;
}
// Debug logging
function logDebug(message, type = 'info') {
const debugLog = document.getElementById('debug-log');
const entry = document.createElement('div');
entry.className = 'debug-entry';
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
entry.innerHTML = \`
<span class="debug-timestamp">\${timestamp}</span>
<span class="debug-type \${type}">\${type.toUpperCase()}</span>
<span class="debug-message">\${message}</span>
\`;
debugLog.insertBefore(entry, debugLog.firstChild);
}
// Initialize on load
window.addEventListener('load', () => {
initializeWidget();
});
</script>
</body>
</html>
const ErrorTypes = {
VALIDATION: 'VALIDATION_ERROR',
NETWORK: 'NETWORK_ERROR',
PAYMENT: 'PAYMENT_ERROR',
WIDGET: 'ERROR',
SYSTEM: 'SYSTEM_ERROR'
};
const ErrorMessages = {
[ErrorTypes.VALIDATION]: {
title: 'Invalid Input',
message: 'Please check your input and try again.',
recoverable: true
},
// ... other error types
};
function handleError(error) {
const errorType = getErrorType(error);
const errorInfo = ErrorMessages[errorType];
showError(
errorInfo.title,
error.message || errorInfo.message,
error.details,
errorInfo.recoverable
);
if (errorInfo.recoverable && retryCount < MAX_RETRIES) {
showRetryButton();
}
}
function retryPayment() {
if (retryCount >= MAX_RETRIES) {
handleError({
type: ErrorTypes.SYSTEM,
message: 'Maximum retry attempts reached',
recoverable: false
});
return;
}
retryCount++;
setTimeout(() => {
resetWidget();
initializeWidget();
}, RETRY_DELAY);
}
function getErrorType(error) {
// Network errors
if (error instanceof TypeError || error.name === 'NetworkError') {
return ErrorTypes.NETWORK;
}
// Validation errors
if (error.code?.startsWith('VALIDATION_')) {
return ErrorTypes.VALIDATION;
}
// Payment errors
if (error.code?.startsWith('PAYMENT_')) {
return ErrorTypes.PAYMENT;
}
return ErrorTypes.SYSTEM;
}
class CircuitBreaker {
constructor() {
this.failures = 0;
this.lastFailure = null;
this.state = 'CLOSED';
}
recordFailure() {
this.failures++;
this.lastFailure = Date.now();
if (this.failures >= MAX_FAILURES) {
this.state = 'OPEN';
}
}
canRetry() {
if (this.state === 'OPEN') {
const timeSinceLastFailure = Date.now() - this.lastFailure;
if (timeSinceLastFailure > RESET_TIMEOUT) {
this.reset();
return true;
}
return false;
}
return true;
}
reset() {
this.failures = 0;
this.lastFailure = null;
this.state = 'CLOSED';
}
}
async function attemptRecovery(error) {
const recoveryStrategies = {
[ErrorTypes.NETWORK]: async () => {
await checkConnectivity();
return retryPayment();
},
[ErrorTypes.VALIDATION]: () => {
resetForm();
return Promise.resolve();
},
[ErrorTypes.PAYMENT]: async () => {
await validatePaymentState();
return retryPayment();
}
};
const strategy = recoveryStrategies[error.type];
if (strategy) {
return strategy();
}
throw new Error('No recovery strategy available');
}