Learn how to diagnose and fix common issues with the Paze web component.
The Paze web component throws specific exceptions for different error scenarios:
| Exception | Error code | Description |
|---|---|---|
PazeLoadFailedException | SDK1200 | The Paze SDK failed to load. For example, due to network issues, blocked resources, or CSP restrictions. |
PazeInitializationFailedException | SDK1206, SDK1207 | Paze SDK initialisation failed, or canCheckout() did not return an eligibility result. |
PazeConfigurationValidationFailedException | SDK1202-1205, SDK1222-1232 | Configuration validation failed. For example, due to a missing client ID, invalid lengths, or incorrect billing preferences. |
PazeDynamicIdentityRequiredException | SDK1208 | Dynamic presentment requires both email and phone number to be provided. |
PazeIdentityValidationFailedException | SDK1209-SDK1212 | Email or phone number validation failed. Check format and length requirements. |
PazeCurrencyNotSupportedException | SDK1213 | Paze only supports USD currency. The provided currency isn't supported. |
PazePaymentFailedException | SDK1214 | Paze checkout or wallet flow failed before authorisation. For example, due to network issues or an error returned by the Paze SDK. |
PazeTransactionFailedException | SDK1235 | Transaction submission failed. The exception includes a submitResult with error details. onSubmitError receives the same failure data. |
PazeIncompleteCheckoutException | SDK1221 | Paze complete() was called before checkout() finished successfully. |
PazeCheckoutResponseMissingException | SDK1215 | Paze checkout did not return a checkoutResponse for a COMPLETE result. |
PazeCompleteResponseMissingException | SDK1216 | Paze complete() did not return a completeResponse. |
PazeCheckoutDecodeFailedException | SDK1217 | Unable to decode the Paze checkoutResponse. |
PazeCompleteDecodeFailedException | SDK1218 | Unable to decode the Paze completeResponse. |
PazeCompleteSecuredPayloadMissingException | SDK1219 | Paze completeResponse did not contain a securedPayload for decryption. |
PazeDecryptFailedException | SDK1233 | Token decryption failed. Check session configuration and Paze wallet settings in Unity Portal. |
PazeAccountInaccessibleException | SDK1220 | Paze wallet account exists but is not accessible to the merchant. |
PazeInvalidRequestException | SDK1234 | Invalid request parameters or configuration returned by the Paze SDK. |
UnsupportedFundingTypePazeSdkException | SDK0118 | Paze is not included in the allowed funding types for this session. |
User cancellation through onCheckoutIncomplete is not thrown as an exception. Use the callback to handle cancellations gracefully.
Use a single component configuration that routes onError, onCheckoutIncomplete, and onSubmitError to appropriate user messaging and logging:
const config = {
onError: (error) => {
// Log error for debugging
console.error('Paze error:', {
name: error.name,
message: error.message,
code: error.ErrorCode, // Note: Capital 'E' in ErrorCode
stack: error.stack,
timestamp: new Date().toISOString()
});
// Handle specific error types by exception name
if (error.name === 'PazeLoadFailedException') {
showUserMessage('Unable to load Paze. Please check your connection and try again.');
offerAlternativePaymentMethods();
} else if (error.name === 'PazeCurrencyNotSupportedException') {
showUserMessage('Paze is only available for USD transactions. Please use an alternative payment method.');
showAlternativePaymentMethods();
} else if (error.name === 'PazeIdentityValidationFailedException') {
showUserMessage('Please provide a valid email address to use Paze.');
highlightEmailField();
} else if (error.name === 'PazeDynamicIdentityRequiredException') {
showUserMessage('Please provide both email address and phone number to use Paze.');
highlightIdentityFields();
} else if (error.name === 'PazeConfigurationValidationFailedException') {
showUserMessage('Payment system temporarily unavailable. Please try again in a few moments.');
logCriticalError('Configuration validation error', error);
} else if (error.name === 'PazePaymentFailedException') {
showUserMessage('Payment failed. Please check your payment information and try again.');
offerRetryOption();
} else if (error.name === 'PazeDecryptFailedException') {
showUserMessage('Payment processing error. Please try again.');
logCriticalError('Token decryption error', error);
} else if (error.name === 'PazeAccountInaccessibleException') {
showUserMessage('Unable to access your Paze wallet. Please use an alternative payment method.');
showAlternativePaymentMethods();
} else if (error.name === 'PazeInitializationFailedException') {
showUserMessage('Payment system not ready. Please refresh and try again.');
logCriticalError('Paze initialization error', error);
} else {
showUserMessage('An unexpected error occurred. Please try again or contact support.');
logUnknownError(error);
}
},
// Handle checkout cancellation separately (not an exception)
onCheckoutIncomplete: (result) => {
console.log('Paze checkout cancelled or incomplete');
trackEvent('paze_checkout_cancelled');
showUserMessage('Payment was cancelled. You can try again when ready.');
},
// Handle submission errors (FailedSubmitResult, PazeTransactionFailedException, or BaseSdkException)
onSubmitError: (submitError) => {
console.error('Paze submission error:', submitError);
const failed = 'submitResult' in submitError
? submitError.submitResult
: 'errorCode' in submitError
? submitError
: null;
if (failed?.errorCode === 'DECLINED') {
showUserMessage('Payment was declined. Please try a different payment method.');
} else if (failed?.errorReason?.toLowerCase().includes('insufficient funds')) {
showUserMessage('Insufficient funds. Please try a different payment method.');
} else if ('ErrorCode' in submitError) {
showUserMessage(submitError.message);
} else {
showUserMessage('Payment failed. Please try again or use a different payment method.');
}
}
};
function showUserMessage(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
document.getElementById('payment-container').appendChild(errorDiv);
// Auto-dismiss after 5 seconds
setTimeout(() => {
errorDiv.remove();
}, 5000);
}
function logCriticalError(context, error) {
// Send to error monitoring service
if (window.errorReporting) {
window.errorReporting.captureException(error, { context });
}
}
function offerAlternativePaymentMethods() {
document.getElementById('paze-container').style.display = 'none';
document.getElementById('alternative-payments').style.display = 'block';
}Use this section when Paze is not appearing in the Unity Portal, missing from your session response, or failing during component initialisation with SDK1202. For portal setup steps, see Onboarding.
Symptom: The Paze service does not appear in your site's Services tab.
Solution: Enable Paze at the merchant group level first (Onboarding, Step 1), then return to the site configuration.
Symptom: Session response does not include allowedFundingTypes.wallets.paze.clientId.
Solution:
- Verify the client ID is saved in Paze Account Settings for the correct site.
- Create a new session after saving portal changes.
- Confirm you are using the correct site name in your session request.
Symptom: Component fails with Missing session.allowedFundingTypes.wallets.paze.clientId.
Solution: The session was created before Paze was configured, or Paze is not enabled for the site. Complete Onboarding, Steps 3–4, then re-create the session.
The symptoms of this are:
- The button element is hidden or not rendered.
- There's an empty container where the button should appear.
- No Paze option is visible to customers.
Run these checks in your browser console to narrow down why the button is not visible:
// Step 1: Check Paze SDK availability
console.log('Checking Paze SDK availability...');
// Check if Paze SDK is loaded
if (!window.DIGITAL_WALLET_SDK || !window.DIGITAL_WALLET_SDK.canCheckout) {
console.error('Paze SDK not loaded');
console.log('Ensure the Paze SDK script is included and loaded');
return;
}
// Step 2: Verify session configuration
const sessionPazeConfig = sessionData?.allowedFundingTypes?.wallets?.paze;
if (!sessionPazeConfig) {
console.error('Paze not configured in session');
console.log('Verify Paze is enabled in Unity Portal and included in session response');
return;
}
console.log('Session Paze config:', {
clientId: sessionPazeConfig.clientId ? 'Present' : 'Missing',
profileId: sessionPazeConfig.profileId ? 'Present' : 'Not set'
});
// Step 3: Check browser compatibility
const browserInfo = {
userAgent: navigator.userAgent,
isChrome: /Chrome\/(\d+)/.test(navigator.userAgent),
isSafari: /Safari\//.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
isFirefox: /Firefox\/(\d+)/.test(navigator.userAgent)
};
console.log('Browser info:', browserInfo);
// Step 4: Check HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.error('Paze requires HTTPS in production');
return;
}
// Step 5: Verify component configuration
const pazeConfig = {
buttonRenderMode: config.buttonRenderMode,
emailAddress: config.emailAddress,
phoneNumber: config.phoneNumber
};
console.log('Paze component config:', pazeConfig);
if (config.buttonRenderMode === 'dynamic') {
if (!config.emailAddress || !config.phoneNumber) {
console.error('Dynamic presentment requires both emailAddress and phoneNumber');
return;
}
}
// Step 6: Test presentment
try {
console.log('Testing Paze presentment...');
// Presentment check happens automatically during component creation
// Monitor onPresentmentResolved callback
} catch (error) {
console.error('Paze presentment test failed:', error);
}
console.log('All diagnostic checks complete');Use these patterns to hide Paze gracefully when it is unavailable and to validate identity before dynamic presentment:
// Solution 1: Graceful fallback when Paze not available
function initializePaze() {
if (checkPazeAvailability()) {
renderPazeButton();
} else {
console.log('Paze not available, showing alternatives');
renderAlternativePaymentMethods();
}
}
function checkPazeAvailability() {
return window.DIGITAL_WALLET_SDK &&
window.DIGITAL_WALLET_SDK.canCheckout &&
sessionData?.allowedFundingTypes?.wallets?.paze?.clientId &&
isHttps() &&
hasValidConfiguration();
}
function isHttps() {
return location.protocol === 'https:' || location.hostname === 'localhost';
}
function hasValidConfiguration() {
if (!config.buttonRenderMode) return false;
if (config.buttonRenderMode === 'dynamic') {
return config.emailAddress &&
validateEmail(config.emailAddress) &&
config.phoneNumber &&
/^1\d{10}$/.test(config.phoneNumber);
}
if (config.emailAddress && !validateEmail(config.emailAddress)) return false;
if (config.phoneNumber && !/^1\d{10}$/.test(config.phoneNumber)) return false;
return true;
}
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email) && email.length <= 128;
}
function renderAlternativePaymentMethods() {
const container = document.getElementById('payment-buttons');
container.innerHTML = `
<button class="credit-card-button">Pay with Credit Card</button>
<button class="google-pay-button">Pay with Google Pay</button>
`;
}
// Solution 2: Validate identity for dynamic presentment
function initializePazeWithValidation() {
const email = document.getElementById('customer-email')?.value;
const phone = document.getElementById('customer-phone')?.value;
if (!email || !phone) {
console.warn('Dynamic presentment requires both email and phone number');
document.getElementById('paze-container').style.display = 'none';
document.getElementById('identity-required-message').style.display = 'block';
return;
}
if (!validateEmail(email)) {
console.error('Invalid email format:', email);
showError('Please enter a valid email address');
return;
}
if (!/^1\d{10}$/.test(phone)) {
console.error('Invalid phone format:', phone);
showError('Please enter a valid US phone number without +, for example 15551234567');
return;
}
const pazeButton = pxpSdk.create('paze-button', {
buttonRenderMode: 'dynamic',
emailAddress: email,
phoneNumber: phone,
onPresentmentResolved: (isVisible) => {
if (!isVisible) {
console.log('No Paze wallet found for this email');
document.getElementById('paze-container').style.display = 'none';
}
},
onError: (error) => {
console.error('Paze error:', error);
handlePazeError(error);
}
});
pazeButton.mount('paze-container');
}
// Solution 3: Progressive loading with timeout
function loadPazeWithFallback() {
const timeout = setTimeout(() => {
console.warn('Paze SDK loading timeout');
renderAlternativePaymentMethods();
}, 5000); // 5 second timeout
// Check if Paze SDK is already loaded
if (window.DIGITAL_WALLET_SDK) {
clearTimeout(timeout);
initializePaze();
return;
}
// Wait for Paze SDK to load
const checkInterval = setInterval(() => {
if (window.DIGITAL_WALLET_SDK) {
clearInterval(checkInterval);
clearTimeout(timeout);
console.log('Paze SDK loaded successfully');
initializePaze();
}
}, 100);
}The symptoms of this are:
onPresentmentResolvedcallback never fires.- Button remains in loading state indefinitely.
- No visibility determination for Paze button.
In static mode, onPresentmentResolved(true) fires after successful initialisation. In dynamic mode, isVisible is false when canCheckout() reports the shopper has no eligible Paze wallet.
Add logging callbacks to trace initialisation, presentment, and validation errors:
// Enhanced presentment diagnostics
const pazeConfig = {
buttonRenderMode: 'static',
emailAddress: 'customer@example.com',
onInit: () => {
console.log('✅ Paze component initialized');
},
onPresentmentResolved: (isVisible) => {
console.group('🔍 Paze Presentment Diagnostics');
console.log('Presentment resolved:', {
isVisible,
timestamp: new Date().toISOString()
});
if (!isVisible) {
console.log('ℹ️ Reasons wallet might not be visible:');
console.log('- No Paze wallet registered for this email');
console.log('- Email not verified in Paze system');
console.log('- Paze service temporarily unavailable');
}
console.groupEnd();
},
onError: (error) => {
console.error('Presentment error:', error);
if (error.ErrorCode === 'SDK1206' || error.ErrorCode === 'SDK1207') {
console.error('Paze initialization failed');
console.log('Check: clientId in session configuration');
console.log('Check: Network connectivity to Paze servers');
} else if (error.ErrorCode === 'SDK1209' || error.ErrorCode === 'SDK1210') {
console.error('Email validation failed:', error.message);
console.log('Check: Email format (RFC 5322)');
console.log('Check: Email length (max 128 characters)');
}
}
};
// Add timeout for presentment
let presentmentTimeout = setTimeout(() => {
console.warn('Presentment timeout after 10 seconds');
console.log('Paze presentment did not resolve in expected time');
// Hide button or show alternative
document.getElementById('paze-container').style.display = 'none';
}, 10000);
// Clear timeout when presentment resolves
pazeConfig.onPresentmentResolved = (isVisible) => {
clearTimeout(presentmentTimeout);
// ... rest of handler
};Implement a presentment timeout and validate identity before creating the component:
// Solution 1: Implement presentment timeout and fallback
function createPazeWithTimeout() {
let presentmentResolved = false;
const pazeButton = pxpSdk.create('paze-button', {
buttonRenderMode: 'static',
emailAddress: customerEmail,
onPresentmentResolved: (isVisible) => {
presentmentResolved = true;
if (isVisible) {
console.log('Paze wallet available');
document.getElementById('paze-container').style.display = 'block';
} else {
console.log('No Paze wallet found');
document.getElementById('paze-container').style.display = 'none';
document.getElementById('paze-unavailable-message').style.display = 'block';
}
},
onError: (error) => {
presentmentResolved = true;
console.error('Paze presentment error:', error);
document.getElementById('paze-container').style.display = 'none';
}
});
pazeButton.mount('paze-container');
// Timeout fallback
setTimeout(() => {
if (!presentmentResolved) {
console.warn('Paze presentment timeout - hiding button');
document.getElementById('paze-container').style.display = 'none';
}
}, 10000);
}
// Solution 2: Validate email before creating component
function createPazeWithValidation() {
const email = getCustomerEmail();
// Validate email format
if (!email || !validatePazeEmail(email)) {
console.error('Invalid email for Paze');
return;
}
console.log('Creating Paze button with validated email:', email);
const pazeButton = pxpSdk.create('paze-button', {
buttonRenderMode: 'static',
emailAddress: email,
onPresentmentResolved: handlePresentment
});
pazeButton.mount('paze-container');
}
function validatePazeEmail(email) {
// Check format (RFC 5322 compliant)
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
console.error('Email format invalid');
return false;
}
// Check length
if (email.length > 128) {
console.error('Email too long (max 128 characters)');
return false;
}
return true;
}
function handlePresentment(isVisible) {
if (isVisible) {
// Show Paze button
document.getElementById('paze-container').classList.add('visible');
trackEvent('paze_wallet_available');
} else {
// Hide Paze, offer alternatives
document.getElementById('paze-container').style.display = 'none';
document.getElementById('alternative-payment-methods').style.display = 'block';
trackEvent('paze_wallet_not_available');
}
}The symptoms of this are:
- Customer clicks Paze button but checkout doesn't complete.
onCheckoutIncompletefires unexpectedly.- Paze wallet opens but customer can't complete payment.
Instrument the checkout callbacks to log each step and capture the incomplete reason:
// Comprehensive checkout flow diagnostics
const pazeConfig = {
buttonRenderMode: 'static',
emailAddress: 'customer@example.com',
onPazeButtonClicked: (event) => {
console.group('🔍 Paze Checkout Flow');
console.log('Step 1: Button clicked');
console.log('Email:', config.emailAddress);
console.log('Currency:', pxpSdk.getConfig().transactionData.currency);
console.log('Amount:', pxpSdk.getConfig().transactionData.amount);
},
onCheckoutComplete: (result) => {
console.log('Step 2: Checkout complete');
console.log('Result:', result.result);
console.log('Session ID:', result.checkoutDecodedResponse?.sessionId);
console.log('Card network:', result.checkoutDecodedResponse?.maskedCard?.paymentCardNetwork);
console.groupEnd();
return true;
},
onCheckoutIncomplete: (result) => {
console.warn('Checkout incomplete or cancelled');
console.log('Reason:', result.reason);
console.log('Session ID:', result.checkoutDecodedResponse?.sessionId);
console.groupEnd();
if (result.reason?.toLowerCase().includes('cancel')) {
trackEvent('paze_user_cancelled');
} else {
trackEvent('paze_checkout_error', { reason: result.reason });
}
},
onError: (error) => {
console.error('Checkout error:', error);
console.groupEnd();
if (error.ErrorCode === 'SDK1213') {
console.error('Currency not supported - Paze only supports USD');
} else if (error.ErrorCode === 'SDK1214') {
console.error('Checkout failed:', error.message);
}
}
};Validate currency before mounting the component and handle incomplete checkout with clear customer messaging:
// Solution 1: Currency validation before checkout
function initializePazeWithCurrencyCheck() {
const currency = pxpSdk.getConfig().transactionData.currency;
if (currency !== 'USD') {
console.warn('Paze only supports USD, current currency:', currency);
document.getElementById('paze-container').style.display = 'none';
document.getElementById('currency-notice').textContent =
`Paze is only available for USD transactions. Current currency: ${currency}`;
return;
}
const pazeButton = pxpSdk.create('paze-button', {
buttonRenderMode: 'static',
emailAddress: customerEmail,
onCheckoutComplete: handleCheckoutComplete,
onCheckoutIncomplete: handleCheckoutIncomplete
});
pazeButton.mount('paze-container');
}
function handleCheckoutComplete(result) {
console.log('Paze checkout successful');
// Show success indicator
showCheckoutSuccess();
// Proceed with decryption
return true;
}
function handleCheckoutIncomplete(result) {
console.log('Checkout incomplete:', result.reason);
if (result.reason?.toLowerCase().includes('cancel')) {
showMessage('Payment cancelled. You can try again when ready.');
} else {
showMessage('Unable to complete checkout. Please try a different payment method.');
showAlternativePaymentMethods();
}
}
// Solution 2: Implement checkout timeout handling
function createPazeWithCheckoutTimeout() {
let checkoutTimeout;
const pazeButton = pxpSdk.create('paze-button', {
buttonRenderMode: 'static',
emailAddress: customerEmail,
onPazeButtonClicked: () => {
console.log('Starting checkout, setting 5 minute timeout');
// Set 5-minute timeout for checkout
checkoutTimeout = setTimeout(() => {
console.warn('Checkout timeout - 5 minutes elapsed');
showMessage('Checkout timed out. Please try again.');
hideLoadingIndicator();
}, 5 * 60 * 1000);
showLoadingIndicator('Opening Paze wallet...');
},
onCheckoutComplete: (result) => {
clearTimeout(checkoutTimeout);
console.log('Checkout completed successfully');
return true;
},
onCheckoutIncomplete: (result) => {
clearTimeout(checkoutTimeout);
console.log('Checkout incomplete');
hideLoadingIndicator();
},
onError: (error) => {
clearTimeout(checkoutTimeout);
console.error('Checkout error:', error);
hideLoadingIndicator();
}
});
pazeButton.mount('paze-container');
}The symptoms of this are:
- Checkout completes but payment doesn't process.
onPostDecryptionnever fires.- Error: "Token decryption failed" (
SDK1233).
Log each stage of the decryption flow to see whether SDK-managed or manual decryption is failing:
// Decryption flow diagnostics
const pazeConfig = {
buttonRenderMode: 'static',
emailAddress: 'customer@example.com',
onCheckoutComplete: (result) => {
console.group('🔐 Decryption Flow Diagnostics');
console.log('Step 1: Checkout complete, preparing for decryption');
return true;
},
onPreDecryption: () => {
console.log('Step 2: Pre-decryption');
console.log('Decryption method: SDK-managed (automatic)');
// Check session encryption key
const session = pxpSdk.getConfig().session;
console.log('Session encryption key present:', !!session.encryptionKey);
return true;
},
onPostDecryption: (decryptedPayload) => {
console.log('Step 3: Post-decryption');
console.log('Decryption successful');
console.log('Network token present:', !!decryptedPayload.fundingData?.networkToken);
console.groupEnd();
},
onError: (error) => {
console.error('Decryption error:', error);
console.groupEnd();
if (error.ErrorCode === 'SDK1233') {
console.error('Token decryption failed');
console.log('Check: Session encryption key');
console.log('Check: Gateway configuration in Unity Portal');
console.log('Check: Network connectivity');
} else if (error.ErrorCode === 'SDK1219') {
console.error('Secured payload missing from complete response');
}
}
};
// Manual decryption diagnostics
const manualDecryptionConfig = {
onPreDecryption: () => {
console.log('Manual decryption mode enabled');
return false;
},
onComplete: async (result) => {
console.group('Manual Decryption Diagnostics');
const securedPayload = result.completeDecodedResponse?.securedPayload;
console.log('Secured payload received');
console.log('Has secured payload:', !!securedPayload);
console.log('Payload ID:', result.completeDecodedResponse?.payloadId);
try {
// Forward securedPayload to your backend (never call PXP decrypt API from the browser)
console.log('Calling merchant decrypt endpoint...');
const response = await fetch('/api/paze/decrypt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ securedPayload })
});
console.log('Decryption API response status:', response.status);
if (!response.ok) {
console.error('Decryption API failed:', await response.text());
} else {
console.log('Manual decryption successful');
}
} catch (error) {
console.error('Manual decryption error:', error);
}
console.groupEnd();
}
};Verify the session before decryption, recover from session expiry, and handle manual decryption with retries. For the PXP decrypt-token API, see Backend decryption.
// Solution 1: Verify session encryption key
function verifySessionForPaze() {
const session = pxpSdk.getConfig().session;
if (!session.encryptionKey) {
console.error('Session encryption key missing');
showError('Payment system configuration error. Please refresh and try again.');
return false;
}
if (!session.sessionId) {
console.error('Session ID missing');
showError('Session invalid. Please refresh and try again.');
return false;
}
// Check session expiry when your backend includes sessionExpiry in the session response
if (session.sessionExpiry) {
const expiresAt = new Date(session.sessionExpiry);
const now = new Date();
if (expiresAt < now) {
console.error('Session expired');
showError('Session expired. Please refresh and try again.');
setTimeout(() => window.location.reload(), 2000);
return false;
}
}
console.log('Session validated for Paze decryption');
return true;
}
// Solution 2: Implement decryption error recovery
const pazeConfig = {
onPreDecryption: () => {
// Verify session before decryption
if (!verifySessionForPaze()) {
return false;
}
return true;
},
onError: async (error) => {
if (error.ErrorCode === 'SDK1233') {
console.error('Decryption failed, attempting recovery');
// Check if session needs refresh
const session = pxpSdk.getConfig().session;
if (session.sessionExpiry) {
const expiresAt = new Date(session.sessionExpiry);
const now = new Date();
const minutesUntilExpiry = (expiresAt - now) / (1000 * 60);
if (minutesUntilExpiry < 5) {
showMessage('Session expired. Refreshing...');
try {
const newSession = await fetch('/api/sessions').then(r => r.json());
// Reinitialise the SDK with the refreshed session
pxpSdk = PxpCheckout.initialize({
...existingSdkConfig,
session: newSession
});
showMessage('Please try your payment again.');
return;
} catch (refreshError) {
showError('Unable to refresh session. Please reload the page.');
return;
}
}
}
showError('Decryption failed. Please try again or use a different payment method.');
}
}
};
// Solution 3: Manual decryption with error handling
const manualDecryptionConfig = {
onPreDecryption: () => {
return false;
},
onComplete: async (result) => {
try {
showLoadingIndicator('Processing payment...');
const securedPayload = result.completeDecodedResponse?.securedPayload;
// Validate payload
if (!securedPayload) {
throw new Error('Secured payload missing');
}
// Call your backend decrypt endpoint with retry
const decryptedData = await retryDecryption({ securedPayload }, 3);
console.log('Manual decryption successful');
// Submit payment with decrypted data
const paymentResult = await submitPayment(decryptedData);
if (paymentResult.success) {
window.location.href = '/success';
} else {
showError('Payment failed: ' + paymentResult.error);
}
} catch (error) {
console.error('Manual decryption failed:', error);
showError('Unable to process payment. Please try again or use a different payment method.');
} finally {
hideLoadingIndicator();
}
}
};
async function retryDecryption(result, maxAttempts) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
console.log(`Decryption attempt ${attempt}/${maxAttempts}`);
const response = await fetch('/api/paze/decrypt', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-HMAC-Signature': await generateHmacSignature(result.securedPayload)
},
body: JSON.stringify({
securedPayload: result.securedPayload
})
});
if (!response.ok) {
throw new Error(`Decryption API returned ${response.status}`);
}
return await response.json();
} catch (error) {
console.warn(`Attempt ${attempt} failed:`, error.message);
if (attempt < maxAttempts) {
// Exponential backoff
await delay(Math.min(1000 * Math.pow(2, attempt), 5000));
} else {
throw error;
}
}
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}The symptoms of this are:
- Decryption succeeds but authorisation fails.
onPostAuthorisationreceives astateother thanAuthorised(when configured).onSubmitErrorfires for HTTP or validation failures.- Transaction declined or rejected.
Configure onPostAuthorisation on the component to receive authorisation outcomes. HTTP failures route to onSubmitError instead.
Log transaction data before submit and inspect the authorisation response state:
// Authorisation flow diagnostics
const pazeConfig = {
onPreAuthorisation: async () => {
console.group('💳 Payment authorisation diagnostics');
console.log('Step 1: Pre-authorisation');
// Check transaction data
const transactionData = pxpSdk.getConfig().transactionData;
console.log('Transaction data:', {
amount: transactionData.amount,
currency: transactionData.currency,
merchantTransactionId: transactionData.merchantTransactionId
});
// Validate amount
if (transactionData.amount <= 0) {
console.error('Invalid amount:', transactionData.amount);
return false; // Cancel authorisation
}
console.log('Pre-authorisation checks passed');
return {
riskScreeningData: {
performRiskScreening: true,
userIp: "192.168.1.100"
}
};
},
onPostAuthorisation: (data) => {
console.log('Step 2: Post-authorisation');
const isSuccess = data.state === 'Authorised';
console.log('Result type:', isSuccess ? 'Success' : 'Failure');
if (isSuccess) {
console.log('Authorisation successful');
console.log('Merchant transaction ID:', data.merchantTransactionId);
console.log('System transaction ID:', data.systemTransactionId);
} else {
console.error('Authorisation failed');
console.error('State:', data.state);
console.error('Provider response code:', data.providerResponseCode);
console.error('State data code:', data.stateDataCode);
if (data.state === 'Declined') {
console.error('Payment declined by issuer');
}
}
console.groupEnd();
},
onSubmitError: (submitError) => {
const failed = 'submitResult' in submitError
? submitError.submitResult
: 'errorCode' in submitError
? submitError
: null;
console.error('Submit error:', failed ?? submitError);
}
};Verify successful authorisation on your backend before fulfilling the order and track declined states separately from submission errors:
// Solution 1: Comprehensive authorization handling
const pazeConfig = {
onPostAuthorisation: async (data) => {
if (data.state === 'Authorised') {
console.log('Payment authorized successfully');
// Verify on backend before fulfilling
const verified = await verifyPaymentOnBackend({
systemTransactionId: data.systemTransactionId,
merchantTransactionId: data.merchantTransactionId
});
if (verified.success) {
showSuccessMessage('Payment successful!');
setTimeout(() => {
window.location.href = `/success?orderId=${verified.orderId}`;
}, 2000);
} else {
showError('Payment verification failed. Please contact support.');
}
} else {
showError(`Payment failed: ${data.state || 'Unknown error'}`);
trackEvent('paze_authorization_failed', {
state: data.state,
providerResponseCode: data.providerResponseCode
});
// Show alternative payment methods
showAlternativePaymentMethods();
}
},
onSubmitError: (submitError) => {
console.error('Payment submission error:', submitError);
const message = 'submitResult' in submitError
? submitError.submitResult.errorReason
: 'errorCode' in submitError
? submitError.errorReason
: submitError.message;
showError(message || 'Unable to process payment. Please try again.');
trackEvent('paze_submit_error', {
errorCode: 'submitResult' in submitError
? submitError.submitResult.errorCode
: 'errorCode' in submitError
? submitError.errorCode
: submitError.ErrorCode
});
}
};
async function verifyPaymentOnBackend(transactionData) {
try {
const response = await fetch('/api/verify-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(transactionData)
});
return await response.json();
} catch (error) {
console.error('Backend verification failed:', error);
return { success: false, error: error.message };
}
}
// Solution 2: Implement retry logic for transient failures
async function handleAuthorizationWithRetry(data) {
if (data.state !== 'Authorised') {
// Use onSubmitError for submission-level failures; check state for authorisation outcome
trackEvent('paze_authorization_failed', {
state: data.state,
providerResponseCode: data.providerResponseCode
});
}
}To get more detailed information about errors, enable verbose logging and run environment checks before reproducing an issue:
// Enable comprehensive logging
window.pazeDebug = {
logLevel: 'verbose',
logPaymentData: false, // Never log actual payment tokens
logNetworkRequests: true,
logPresentment: true,
logDecryption: false // Security: never log decryption details
};
// Debug helper functions
const debugPaze = {
checkEnvironment() {
console.group('🔍 Paze Environment Check');
console.log('Protocol:', location.protocol);
console.log('Hostname:', location.hostname);
console.log('User Agent:', navigator.userAgent);
console.log('Paze SDK Available:', !!(window.DIGITAL_WALLET_SDK));
console.log('Environment:', pxpSdk.environment);
console.log('Currency:', pxpSdk.getConfig().transactionData.currency);
console.groupEnd();
},
logConfiguration(config) {
console.group('⚙️ Paze Configuration');
console.log('Button Render Mode:', config.buttonRenderMode);
console.log('Email Address:', config.emailAddress ? '***@***.***' : 'Not provided'); // Masked
console.log('Phone Number:', config.phoneNumber ? '***-***-****' : 'Not provided'); // Masked
console.log('Billing Preference:', config.billingPreference);
console.log('Accepted Card Networks:', config.acceptedPaymentCardNetworks);
console.log('Perform AVS:', config.performAVS);
console.log('Cobrand:', config.cobrand);
console.groupEnd();
},
logSessionConfig() {
console.group('📋 Session Paze Configuration');
const pazeConfig = pxpSdk.getConfig().session?.allowedFundingTypes?.wallets?.paze;
if (!pazeConfig) {
console.error('❌ Paze not configured in session');
} else {
console.log('✅ Paze configuration present');
console.log('Client ID:', pazeConfig.clientId ? 'Present' : 'Missing');
console.log('Profile ID:', pazeConfig.profileId ? 'Present' : 'Not set');
}
console.groupEnd();
},
testPazeSDK() {
console.group('Paze SDK test');
if (!window.DIGITAL_WALLET_SDK) {
console.error('Paze SDK not loaded');
console.log('Check: Script tag is present in HTML');
console.log('Check: Network requests for Paze SDK');
} else {
console.log('Paze SDK loaded');
console.log('DIGITAL_WALLET_SDK.canCheckout available:', typeof window.DIGITAL_WALLET_SDK.canCheckout === 'function');
console.log('DIGITAL_WALLET_SDK.checkout available:', typeof window.DIGITAL_WALLET_SDK.checkout === 'function');
}
console.groupEnd();
},
logPresentmentFlow(config) {
console.group('Paze Presentment Flow');
console.log('Email:', config.emailAddress ? '***@***.***' : 'Not provided');
console.log('Phone:', config.phoneNumber ? '***-***-****' : 'Not provided');
console.log('Render mode:', config.buttonRenderMode);
console.log('Waiting for presentment resolution...');
console.groupEnd();
}
};
// Use in your implementation
debugPaze.checkEnvironment();
debugPaze.logSessionConfig();
debugPaze.testPazeSDK();
debugPaze.logConfiguration(pazeConfig);Run pre-flight checks before creating the Paze button to catch configuration issues early:
// Pre-flight checks before initializing Paze
async function performPreflightChecks() {
const checks = [
{
name: 'HTTPS check',
test: () => location.protocol === 'https:' || location.hostname === 'localhost',
fix: 'Ensure your site is served over HTTPS'
},
{
name: 'Paze SDK loaded',
test: () => !!(window.DIGITAL_WALLET_SDK && window.DIGITAL_WALLET_SDK.canCheckout),
fix: 'Ensure Paze SDK script is loaded'
},
{
name: 'Session Paze configuration',
test: () => {
const pazeConfig = pxpSdk.getConfig().session?.allowedFundingTypes?.wallets?.paze;
return pazeConfig?.clientId;
},
fix: 'Enable Paze in Unity Portal and ensure it\'s in session response'
},
{
name: 'Currency is USD',
test: () => pxpSdk.getConfig().transactionData.currency === 'USD',
fix: 'Paze only supports USD currency'
},
{
name: 'Identity fields for dynamic presentment',
test: () => {
if (pazeConfig.buttonRenderMode !== 'dynamic') return true;
const email = pazeConfig.emailAddress;
const phone = pazeConfig.phoneNumber;
return email &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) &&
email.length <= 128 &&
phone &&
/^1\d{10}$/.test(phone);
},
fix: 'Dynamic presentment requires valid emailAddress and phoneNumber (US E.164 without +, 11 digits)'
},
{
name: 'Valid transaction amount',
test: () => {
const amount = pxpSdk.getConfig().transactionData.amount;
return !isNaN(amount) && amount > 0;
},
fix: 'Ensure transaction amount is valid'
},
{
name: 'Browser compatibility',
test: () => checkBrowserCompatibility(),
fix: 'Use a supported browser (Chrome 80+, Safari 13.1+, Firefox 75+, Edge 80+)'
}
];
const results = [];
for (const check of checks) {
const passed = await check.test();
results.push({ name: check.name, passed, fix: check.fix });
if (!passed) {
console.warn(`${check.name}: ${check.fix}`);
} else {
console.log(`${check.name}`);
}
}
return results.every(result => result.passed);
}
function checkBrowserCompatibility() {
const ua = navigator.userAgent;
// Chrome 80+
if (/Chrome\/(\d+)/.test(ua)) {
return parseInt(RegExp.$1) >= 80;
}
// Safari 13.1+
if (/Version\/(\d+)\.(\d+)/.test(ua) && /Safari/.test(ua)) {
const major = parseInt(RegExp.$1);
const minor = parseInt(RegExp.$2);
return major > 13 || (major === 13 && minor >= 1);
}
// Firefox 75+
if (/Firefox\/(\d+)/.test(ua)) {
return parseInt(RegExp.$1) >= 75;
}
// Edge 80+
if (/Edg\/(\d+)/.test(ua)) {
return parseInt(RegExp.$1) >= 80;
}
return false;
}
// Use before initialisation
async function initializePaze() {
const preflightPassed = await performPreflightChecks();
if (preflightPassed) {
const pazeButton = pxpSdk.create('paze-button', pazeConfig);
pazeButton.mount('paze-container');
} else {
console.warn('Preflight checks failed, showing alternative payment methods');
showFallbackPaymentOptions();
}
}Wire Paze callbacks into your error monitoring and analytics services:
// Error monitoring setup
const errorMonitoring = {
captureError(error, context) {
// Send to your error tracking service
const errorData = {
message: error.message,
name: error.name,
errorCode: error.ErrorCode,
stack: error.stack,
context: context,
userAgent: navigator.userAgent,
url: location.href,
timestamp: new Date().toISOString(),
userId: getCurrentUserId(),
sessionId: getSessionId()
};
// Example: Send to Sentry, LogRocket, etc.
this.sendToErrorService(errorData);
// Alert on critical errors
if (this.isCriticalError(error)) {
this.alertOpsTeam(errorData);
}
},
isCriticalError(error) {
const criticalCodes = [
'SDK1200', // SDK load failed
'SDK1206', // Initialisation failed
'SDK1233' // Decryption failed
];
return criticalCodes.includes(error.ErrorCode);
},
trackPazeEvent(eventType, data) {
// Track Paze specific events
const eventData = {
type: eventType,
data: data,
timestamp: new Date().toISOString(),
sessionId: getSessionId()
};
this.sendToAnalytics(eventData);
},
sendToErrorService(data) {
// Example: Sentry
if (window.Sentry) {
Sentry.captureException(new Error(data.message), {
extra: data
});
}
},
sendToAnalytics(data) {
// Example: Google Analytics
if (window.gtag) {
gtag('event', data.type, data.data);
}
},
alertOpsTeam(errorData) {
// Send alert to operations team
fetch('/api/alerts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorData)
});
}
};
// Integration with Paze component
const config = {
onError: (error) => {
errorMonitoring.captureError(error, { component: 'Paze' });
handlePazeError(error);
},
onPresentmentResolved: (isVisible) => {
errorMonitoring.trackPazeEvent('presentment_resolved', {
isVisible
});
},
onCheckoutComplete: (result) => {
errorMonitoring.trackPazeEvent('checkout_complete', {
sessionId: result.checkoutDecodedResponse?.sessionId,
cardNetwork: result.checkoutDecodedResponse?.maskedCard?.paymentCardNetwork
});
return true;
},
onPostAuthorisation: (data) => {
errorMonitoring.trackPazeEvent('payment_completed', {
success: data.state === 'Authorised',
merchantTransactionId: data.merchantTransactionId
});
}
};