Learn how to diagnose and fix common issues with card payment components.
| Issue | Description | Next steps |
|---|---|---|
| Component not rendering. | Card input fields don't appear in the container. |
|
| Validation errors. | Card number or other fields show validation errors. |
|
| 3DS challenge not appearing. | 3D Secure authentication modal doesn't show. |
|
| Tokenisation fails. | Card tokenisation fails before transaction. |
|
| Styling issues. | Custom styles not applying to components. |
|
| Submit button disabled. | Submit button remains disabled even with valid data. |
|
The card components throw specific exceptions for different error scenarios:
| Error Code | Exception | Description | Prevention |
|---|---|---|---|
SDK0200 | ComponentException | Generic component error. | Validate configuration before component creation. |
SDK0201 | ComponentContainerNotFoundException | The container element doesn't exist in the DOM. | Ensure container exists before calling mount(). |
SDK0202 | ComponentNameEmptyException | Component name is missing or empty. | Always provide valid component type to create(). |
SDK0301 | TokenizeCardException | Card tokenisation failed. | Verify API connectivity and session validity. |
SDK0400 | ThreeDSecureException | 3D Secure authentication error. | Check 3DS configuration and network access. |
SDK0401 | PreInitiateAuthenticationException | Pre-authentication request failed. | Verify transaction data and session. |
SDK0402 | InitiateAuthenticationException | Authentication initiation failed. | Check 3DS server connectivity. |
SDK0403 | AuthenticationRequiredException | 3DS authentication required but not completed. | Implement 3DS flow properly. |
SDK0404 | AuthenticationChallengeException | Challenge presentation failed. | Ensure iframe access and proper event handling. |
SDK0405 | AuthenticationRejected Exception | Card issuer rejected authentication. | Inform customer to contact their bank. |
SDK0500 | NetworkException | Network connectivity error. | Implement retry logic and check internet connection. |
SDK0501 | ValidationException | Field validation failed. | Display specific validation messages to user. |
Symptoms:
- Empty containers where card fields should be.
- No visible input fields for card number, expiry, CVV.
- Console errors about missing containers.
// Step 1: Verify DOM is ready
console.log('Checking DOM readiness...');
const container = document.getElementById('card-number-container');
if (!container) {
console.error('Container not found in DOM');
} else {
console.log('Container exists:', container);
}
// Step 2: Verify SDK initialisation
console.log('Checking SDK initialisation...');
if (typeof pxpSdk === 'undefined') {
console.error('PxpCheckout SDK not initialised');
} else {
console.log('SDK initialised successfully');
}
// Step 3: Check component creation
try {
const cardNumber = pxpSdk.create('card-number');
console.log('Card number component created:', cardNumber);
// Try to mount
cardNumber.mount('card-number-container');
console.log('Card number component mounted successfully');
} catch (error) {
console.error('Component creation/mount failed:', error);
}
// Step 4: Check for iframe creation
setTimeout(() => {
const iframe = container.querySelector('iframe');
if (iframe) {
console.log('Iframe created successfully');
} else {
console.error('No iframe found - component may not have mounted');
}
}, 500);Ensure proper DOM timing:
// ❌ Bad - Mount before DOM is ready
const cardNumber = pxpSdk.create('card-number');
cardNumber.mount('card-number-container'); // May fail if DOM not ready
// ✅ Good - Wait for DOM
document.addEventListener('DOMContentLoaded', () => {
const cardNumber = pxpSdk.create('card-number');
cardNumber.mount('card-number-container');
});
// ✅ Better - Check container exists
function mountCardComponent() {
const container = document.getElementById('card-number-container');
if (!container) {
console.error('Container not found, retrying...');
setTimeout(mountCardComponent, 100);
return;
}
const cardNumber = pxpSdk.create('card-number');
cardNumber.mount('card-number-container');
}
// ✅ Best - Use framework lifecycle hooks
// React
useEffect(() => {
if (containerRef.current && pxpSdk) {
const cardNumber = pxpSdk.create('card-number');
cardNumber.mount(containerRef.current);
return () => cardNumber.unmount();
}
}, [pxpSdk]);Handle SDK initialisation errors:
let pxpSdk;
try {
pxpSdk = PxpCheckout.initialize({
environment: 'production',
session: sessionData,
ownerId: 'MERCHANT-1',
ownerType: 'MerchantGroup'
});
console.log('SDK initialised successfully');
} catch (error) {
console.error('SDK initialisation failed:', error);
// Show user-friendly error
showError('Payment system unavailable. Please try again later.');
// Log to monitoring service
logError('SDK_INIT_FAILED', error);
return; // Don't proceed with component creation
}
// Now safe to create components
const cardNumber = pxpSdk.create('card-number');Verify container visibility:
function diagnoseContainerVisibility() {
const container = document.getElementById('card-number-container');
if (!container) {
console.error('Container not found');
return false;
}
const styles = window.getComputedStyle(container);
const diagnostics = {
display: styles.display,
visibility: styles.visibility,
opacity: styles.opacity,
width: styles.width,
height: styles.height,
position: styles.position,
isInViewport: isElementInViewport(container)
};
console.log('Container diagnostics:', diagnostics);
// Check for common issues
if (styles.display === 'none') {
console.warn('Container has display:none - component won\'t be visible');
}
if (parseInt(styles.width) === 0 || parseInt(styles.height) === 0) {
console.warn('Container has zero dimensions');
}
return true;
}
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}Symptoms:
- Valid card numbers show as invalid.
- Error messages appear immediately on input.
- Unable to proceed with payment.
// Test card validation logic
function testCardValidation(cardNumber) {
console.group('Card Validation Test');
console.log('Testing card number:', cardNumber);
// Check Luhn algorithm
const isLuhnValid = validateLuhn(cardNumber);
console.log('Luhn valid:', isLuhnValid);
// Check card type
const cardType = detectCardType(cardNumber);
console.log('Detected card type:', cardType);
// Check length
const lengthValid = validateCardLength(cardNumber, cardType);
console.log('Length valid:', lengthValid);
console.groupEnd();
return isLuhnValid && lengthValid;
}
function validateLuhn(cardNumber) {
const digits = cardNumber.replace(/\D/g, '');
let sum = 0;
let isEven = false;
for (let i = digits.length - 1; i >= 0; i--) {
let digit = parseInt(digits[i]);
if (isEven) {
digit *= 2;
if (digit > 9) {
digit -= 9;
}
}
sum += digit;
isEven = !isEven;
}
return sum % 10 === 0;
}
function detectCardType(cardNumber) {
const patterns = {
visa: /^4/,
mastercard: /^5[1-5]|^2[2-7]/,
amex: /^3[47]/,
discover: /^6(?:011|5)/,
diners: /^3(?:0[0-5]|[68])/,
jcb: /^35/
};
for (const [type, pattern] of Object.entries(patterns)) {
if (pattern.test(cardNumber)) {
return type;
}
}
return 'unknown';
}
function validateCardLength(cardNumber, cardType) {
const expectedLengths = {
visa: [13, 16, 19],
mastercard: [16],
amex: [15],
discover: [16, 19],
diners: [14],
jcb: [16, 19]
};
const digits = cardNumber.replace(/\D/g, '');
const expected = expectedLengths[cardType] || [16];
return expected.includes(digits.length);
}
// Test with various card numbers
testCardValidation('4111111111111111'); // Valid Visa
testCardValidation('5555555555554444'); // Valid Mastercard
testCardValidation('378282246310005'); // Valid AmexListen to validation events:
const cardNumberConfig = {
onChange: (event) => {
console.log('Card number changed:', {
complete: event.complete,
valid: event.valid,
brand: event.brand,
error: event.error
});
// Update UI based on validation
if (event.error) {
showFieldError('card-number', event.error.message);
} else {
clearFieldError('card-number');
}
// Update submit button state
updateSubmitButton();
},
onFieldValid: () => {
console.log('Card number is valid');
highlightField('card-number', 'valid');
},
onFieldInvalid: (error) => {
console.log('Card number is invalid:', error);
highlightField('card-number', 'invalid');
}
};
const cardNumber = pxpSdk.create('card-number', cardNumberConfig);
cardNumber.mount('card-number-container');
function showFieldError(fieldName, message) {
const errorElement = document.getElementById(`${fieldName}-error`);
if (errorElement) {
errorElement.textContent = message;
errorElement.style.display = 'block';
}
}
function clearFieldError(fieldName) {
const errorElement = document.getElementById(`${fieldName}-error`);
if (errorElement) {
errorElement.textContent = '';
errorElement.style.display = 'none';
}
}
function highlightField(fieldName, state) {
const container = document.getElementById(`${fieldName}-container`);
if (container) {
container.classList.remove('valid', 'invalid');
if (state !== 'neutral') {
container.classList.add(state);
}
}
}Implement custom validation:
// Add business-specific validation rules
const cardSubmitConfig = {
beforeSubmit: () => {
// Custom validation before tokenisation
const validationErrors = [];
// Check if card is in excluded BIN list
const cardNumber = getCardNumberValue();
if (isExcludedBIN(cardNumber)) {
validationErrors.push('This card type is not accepted');
}
// Check amount limits for card type
const amount = getTransactionAmount();
const cardType = detectCardType(cardNumber);
if (amount > getMaxAmountForCard Type(cardType)) {
validationErrors.push(`Amount exceeds limit for ${cardType} cards`);
}
// Show errors if any
if (validationErrors.length > 0) {
showValidationErrors(validationErrors);
return false; // Prevent submission
}
return true; // Allow submission
},
onSubmitError: (error) => {
if (error.ErrorCode === 'SDK0501') {
// Validation error from SDK
console.error('Validation error:', error.message);
showError(error.message);
}
}
};
function isExcludedBIN(cardNumber) {
const excludedBINs = ['123456', '654321']; // Example
const bin = cardNumber.substring(0, 6);
return excludedBINs.includes(bin);
}
function getMaxAmountForCardType(cardType) {
const limits = {
amex: 10000,
visa: 15000,
mastercard: 15000,
discover: 10000
};
return limits[cardType] || 5000;
}Symptoms:
- Valid future dates show as invalid.
- Past dates are accepted.
- Format errors with MM/YY input.
const expiryConfig = {
onChange: (event) => {
if (!event.valid && event.complete) {
// Card is expired
showFieldError('expiry', 'Card has expired. Please use a different card.');
} else if (event.error) {
showFieldError('expiry', event.error.message);
} else {
clearFieldError('expiry');
}
},
// Additional validation
customValidation: (month, year) => {
const now = new Date();
const currentMonth = now.getMonth() + 1; // 0-indexed
const currentYear = now.getFullYear() % 100; // Last 2 digits
// Check if card expires within 30 days
if (year === currentYear && month === currentMonth) {
showFieldWarning('expiry', 'Card expires soon. Consider using a different card.');
}
// Check if expiry is too far in future (potential typo)
if (year > currentYear + 10) {
showFieldWarning('expiry', 'Please verify the expiry date.');
}
}
};
function showFieldWarning(fieldName, message) {
const warningElement = document.getElementById(`${fieldName}-warning`);
if (warningElement) {
warningElement.textContent = message;
warningElement.style.display = 'block';
}
}Symptoms:
- 3DS challenge window doesn't appear.
- Transaction fails with authentication error.
- Console shows iframe loading errors.
function diagnose3DSSetup() {
console.group('3DS Diagnostic Check');
// Check CSP settings
const csp = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
console.log('CSP meta tag:', csp ? csp.content : 'Not found');
// Check if iframes are blocked
try {
const testIframe = document.createElement('iframe');
testIframe.src = 'about:blank';
document.body.appendChild(testIframe);
console.log('Iframe creation: Allowed');
document.body.removeChild(testIframe);
} catch (error) {
console.error('Iframe creation: Blocked', error);
}
// Check network connectivity to 3DS servers
checkThreeDSConnectivity();
console.groupEnd();
}
async function checkThreeDSConnectivity() {
const threeDSServers = [
'https://threeds2.example.com',
// Add your actual 3DS server URLs
];
for (const server of threeDSServers) {
try {
const response = await fetch(server, { method: 'HEAD', mode: 'no-cors' });
console.log(`3DS Server ${server}: Reachable`);
} catch (error) {
console.error(`3DS Server ${server}: Not reachable`, error);
}
}
}Update Content Security Policy:
<!-- Add 3DS servers to CSP -->
<meta http-equiv="Content-Security-Policy"
content="frame-src 'self' https://threeds2.example.com https://*.3dsecure.io;">Handle 3DS events properly:
const cardSubmitConfig = {
onSubmit: () => {
console.log('Payment submission started');
showLoadingIndicator('Processing payment...');
},
on3DSInitiated: () => {
console.log('3DS authentication initiated');
showLoadingIndicator('Verifying your card...');
},
on3DSChallengePresented: () => {
console.log('3DS challenge presented to user');
hideLoadingIndicator();
showMessage('Please complete authentication in the popup');
},
on3DSComplete: (result) => {
console.log('3DS authentication completed:', result);
if (result.authenticated) {
showLoadingIndicator('Completing payment...');
} else {
showError('Authentication failed. Please try again or use a different card.');
}
},
onPostAuthorisation: (result) => {
console.log('Payment authorised:', result);
hideLoadingIndicator();
// Redirect to success page
window.location.href = `/success?id=${result.systemTransactionId}`;
},
onSubmitError: (error) => {
console.error('Payment failed:', error);
hideLoadingIndicator();
if (error.ErrorCode === 'SDK0505') {
showError('Card authentication failed. Please contact your bank or try a different card.');
} else {
showError(`Payment failed: ${error.message}`);
}
}
};
const cardSubmit = pxpSdk.create('card-submit', cardSubmitConfig);Implement 3DS timeout handling:
let threeDSTimeout;
const cardSubmitConfig = {
on3DSChallengePresented: () => {
// Set 5-minute timeout for 3DS challenge
threeDSTimeout = setTimeout(() => {
console.warn('3DS challenge timeout');
showError('Authentication timed out. Please try again.');
// Cancel the transaction
cancelPayment();
}, 5 * 60 * 1000);
},
on3DSComplete: () => {
// Clear timeout when 3DS completes
if (threeDSTimeout) {
clearTimeout(threeDSTimeout);
threeDSTimeout = null;
}
},
onSubmitError: () => {
// Clear timeout on error
if (threeDSTimeout) {
clearTimeout(threeDSTimeout);
threeDSTimeout = null;
}
}
};Symptoms:
- Payment fails before reaching transaction stage.
- Error: "Unable to tokenise card".
- Network errors in console.
async function diagnoseTokenisation() {
console.group('Tokenisation Diagnostic');
// Check session validity
const sessionData = pxpSdk.getSession();
console.log('Session data:', {
sessionId: sessionData.sessionId ? 'Present' : 'Missing',
expiresAt: sessionData.expiresAt,
isExpired: sessionData.expiresAt ? new Date(sessionData.expiresAt) < new Date() : 'Unknown'
});
// Check HMAC signature
const hmac = sessionData.hmacSignature;
console.log('HMAC signature:', hmac ? 'Present' : 'Missing');
// Check API connectivity
try {
const response = await fetch('https://api.pxp.io/health', { mode: 'no-cors' });
console.log('API connectivity: OK');
} catch (error) {
console.error('API connectivity: Failed', error);
}
// Check token vault URL
const tokenVaultUrl = pxpSdk.getConfig().tokenVaultUrl;
console.log('Token Vault URL:', tokenVaultUrl);
console.groupEnd();
}Handle session expiration:
const cardSubmitConfig = {
beforeSubmit: async () => {
// Check if session is about to expire
const session = pxpSdk.getSession();
const expiresAt = new Date(session.expiresAt);
const now = new Date();
const minutesUntilExpiry = (expiresAt - now) / (1000 * 60);
if (minutesUntilExpiry < 5) {
console.log('Session expiring soon, refreshing...');
try {
// Request new session from backend
const newSession = await fetch('/api/create-session').then(r => r.json());
// Reinitialise SDK with new session
pxpSdk.updateSession(newSession);
console.log('Session refreshed successfully');
return true;
} catch (error) {
console.error('Failed to refresh session:', error);
showError('Your session has expired. Please refresh the page.');
return false;
}
}
return true;
},
onSubmitError: async (error) => {
if (error.message.includes('session') || error.message.includes('expired')) {
// Session expired during submission
showError('Your session has expired. Refreshing...');
// Refresh page after short delay
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
};Implement retry logic:
async function submitPaymentWithRetry(maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Payment attempt ${attempt}/${maxRetries}`);
// Trigger card submit
const result = await cardSubmit.submit();
console.log('Payment successful:', result);
return result;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
// Check if error is retryable
if (isRetryableError(error) && attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Non-retryable error or max retries reached
throw error;
}
}
}
function isRetryableError(error) {
const retryableCodes = ['SDK0500', 'SDK0301']; // Network, tokenisation
const retryableMessages = ['network', 'timeout', 'temporarily unavailable'];
return retryableCodes.includes(error.ErrorCode) ||
retryableMessages.some(msg => error.message.toLowerCase().includes(msg));
}
// Use in submit button handler
document.getElementById('submit-button').addEventListener('click', async () => {
try {
showLoadingIndicator();
const result = await submitPaymentWithRetry();
handleSuccess(result);
} catch (error) {
handleError(error);
} finally {
hideLoadingIndicator();
}
});Symptoms:
- Component styling doesn't match design.
- CSS changes have no effect.
- Inconsistent appearance across components.
Use customComponentCss for iframe fields:
const cardNumberConfig = {
customComponentCss: {
base: {
fontSize: '16px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
color: '#32325d',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
},
valid: {
color: '#32cd32'
}
}
};
const cardNumber = pxpSdk.create('card-number', cardNumberConfig);
cardNumber.mount('card-number-container');Style containers consistently:
/* Container styling (applies to all components) */
.card-field-container {
margin-bottom: 16px;
position: relative;
}
.card-field-container label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #32325d;
font-size: 14px;
}
.card-field-container .field-wrapper {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 12px;
background: #fff;
transition: all 0.2s ease;
}
.card-field-container .field-wrapper:focus-within {
border-color: #5469d4;
box-shadow: 0 0 0 3px rgba(84, 105, 212, 0.1);
}
.card-field-container.invalid .field-wrapper {
border-color: #fa755a;
}
.card-field-container.valid .field-wrapper {
border-color: #32cd32;
}
.card-field-container .error-message {
color: #fa755a;
font-size: 13px;
margin-top: 4px;
display: none;
}
.card-field-container.invalid .error-message {
display: block;
}Apply consistent styling across all fields:
// Create shared styling config
const sharedFieldStyles = {
customComponentCss: {
base: {
fontSize: '16px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
color: '#32325d',
lineHeight: '24px',
'::placeholder': {
color: '#aab7c4'
},
':-webkit-autofill': {
color: '#32325d'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
},
valid: {
color: '#32cd32'
}
}
};
// Apply to all card fields
const cardNumber = pxpSdk.create('card-number', sharedFieldStyles);
const cardExpiry = pxpSdk.create('card-expiry', sharedFieldStyles);
const cardCvv = pxpSdk.create('card-cvv', sharedFieldStyles);
const cardName = pxpSdk.create('card-holder-name', sharedFieldStyles);// Enable debug mode globally
window.pxpDebug = {
enabled: true,
logLevel: 'verbose',
logComponents: true,
logNetworkRequests: true
};
// Custom logger
const logger = {
group(label) {
if (window.pxpDebug?.enabled) {
console.group(`🔍 ${label}`);
}
},
groupEnd() {
if (window.pxpDebug?.enabled) {
console.groupEnd();
}
},
log(message, data) {
if (window.pxpDebug?.enabled) {
console.log(`[PXP] ${message}`, data || '');
}
},
error(message, error) {
console.error(`[PXP ERROR] ${message}`, error);
}
};
// Use in your code
logger.group('Card Component Setup');
logger.log('Creating card number component');
const cardNumber = pxpSdk.create('card-number');
logger.log('Mounting to container', 'card-number-container');
cardNumber.mount('card-number-container');
logger.groupEnd();// Monitor all component events
function monitorComponentEvents(component, componentName) {
const events = ['onChange', 'onFocus', 'onBlur', 'onFieldValid', 'onFieldInvalid'];
events.forEach(eventName => {
if (component[eventName]) {
const originalHandler = component[eventName];
component[eventName] = function(...args) {
console.log(`[${componentName}] ${eventName}`, args);
if (originalHandler) {
return originalHandler.apply(this, args);
}
};
}
});
return component;
}
// Use for debugging
const cardNumber = monitorComponentEvents(
pxpSdk.create('card-number'),
'CardNumber'
);// Intercept and log all API requests
const originalFetch = window.fetch;
window.fetch = async function(...args) {
const [url, options] = args;
// Log request
console.group('🌐 Network Request');
console.log('URL:', url);
console.log('Method:', options?.method || 'GET');
console.log('Headers:', options?.headers);
console.groupEnd();
try {
const response = await originalFetch.apply(this, args);
// Log response
console.group('✅ Network Response');
console.log('URL:', url);
console.log('Status:', response.status);
console.log('Status Text:', response.statusText);
console.groupEnd();
return response;
} catch (error) {
console.group('❌ Network Error');
console.log('URL:', url);
console.error('Error:', error);
console.groupEnd();
throw error;
}
};// Pre-flight checks before initialisation
async function performPreflightChecks() {
const checks = [
{
name: 'Session data validity',
test: () => validateSessionData(),
fix: 'Request new session from backend'
},
{
name: 'DOM containers exist',
test: () => checkContainersExist(),
fix: 'Ensure containers are in the DOM before mounting'
},
{
name: 'Network connectivity',
test: async () => await checkNetworkConnectivity(),
fix: 'Check internet connection'
},
{
name: 'No conflicting scripts',
test: () => checkForConflicts(),
fix: 'Remove conflicting payment libraries'
}
];
const results = [];
for (const check of checks) {
try {
const passed = await check.test();
results.push({ ...check, passed });
if (!passed) {
console.warn(`❌ ${check.name}: ${check.fix}`);
} else {
console.log(`✅ ${check.name}`);
}
} catch (error) {
console.error(`❌ ${check.name} failed:`, error);
results.push({ ...check, passed: false, error });
}
}
return results.every(r => r.passed);
}
function validateSessionData() {
const sessionData = getSessionData();
return sessionData &&
sessionData.sessionId &&
sessionData.hmacSignature &&
new Date(sessionData.expiresAt) > new Date();
}
function checkContainersExist() {
const requiredContainers = [
'card-number-container',
'card-expiry-container',
'card-cvv-container',
'card-submit-container'
];
return requiredContainers.every(id => document.getElementById(id) !== null);
}
async function checkNetworkConnectivity() {
try {
await fetch('/api/health-check', { method: 'HEAD' });
return true;
} catch {
return false;
}
}
function checkForConflicts() {
const conflictingScripts = ['stripe', 'braintree', 'square'];
return !conflictingScripts.some(script => window[script]);
}
// Use before initialisation
async function initializePaymentForm() {
const preflightPassed = await performPreflightChecks();
if (!preflightPassed) {
showError('Unable to initialise payment form. Please refresh the page.');
return;
}
// Safe to proceed
createCardComponents();
}// Comprehensive error tracking
const errorTracking = {
capture(error, context) {
const errorData = {
message: error.message,
code: error.ErrorCode,
stack: error.stack,
context: context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: location.href,
sessionId: getSessionId(),
userId: getUserId()
};
// Send to error monitoring service
this.sendToMonitoring(errorData);
// Track in analytics
this.trackInAnalytics('payment_error', {
error_code: error.ErrorCode,
error_type: error.name,
context: context
});
},
sendToMonitoring(data) {
// Example: Sentry, LogRocket, etc.
if (window.Sentry) {
Sentry.captureException(new Error(data.message), {
extra: data
});
}
},
trackInAnalytics(event, properties) {
// Example: Google Analytics, Mixpanel, etc.
if (window.gtag) {
gtag('event', event, properties);
}
}
};
// Use in component configuration
const cardSubmitConfig = {
onSubmitError: (error) => {
errorTracking.capture(error, 'card_submit');
showUserFriendlyError(error);
}
};