Handling Timeouts
This guide explains how to handle slow or unresponsive payment requests when integrating with the Hosted Payments Page. Because the HPP client library does not impose its own request timeout, your integrating page is responsible for implementing any timeout UX.
hpp.js) does not abort or time out the underlying fetch call that loads the payment form, and it does not fire OnError after any particular wait. If you want users to see a "this is taking too long" message, you must implement the timer in your own code.
Where Timeouts Can Occur
Client-Side
- Initial form load:
Initialize(amount, surcharge)issues an authenticatedGETto the HPP'sPay/Embedendpoint and injects the returned HTML viasrcdoc. The browser's default network timeout applies; the library adds none. - BIN lookup: While the user types the card number, the iframe calls the merchant's BIN service to detect the card brand and surcharge eligibility.
- Payment submission: When the iframe submits the form, the server posts to the backend integration service, which in turn talks to the payment processor.
Server-Side
Server-to-processor calls use HttpClient, which defaults to a 100-second timeout. If the upstream processor is slow, the HPP request can stall until that timeout elapses, after which the iframe renders a "We're Sorry" result page and fires the OnError or OnDecline event via postMessage.
Detecting Slow Responses
Since the library does not fire a timeout event on its own, the pattern is:
- Start a
setTimeoutwhen you callInitializeor when the user clicks submit. - Cancel the timer in
OnSuccess,OnDecline,OnError, andOnCancel. - If the timer fires first, show a timeout message and let the user decide what to do next.
Example: Parent-Side Timeout Timer
var PAYMENT_TIMEOUT_MS = 60000;
var timeoutId = null;
var paymentWidget = new hpp({
Target: "payment-container",
HPPUrl: "https://your-payment-domain.com/",
Token: getAuthToken(),
MerchantId: "merchant-123",
ShowPaymentTypeCc: true,
InitialPaymentType: "CC",
OnSubmit: function (amount) {
// Start a parent-side timer when the user submits
clearTimeout(timeoutId);
timeoutId = setTimeout(displayTimeoutMessage, PAYMENT_TIMEOUT_MS);
showLoadingSpinner();
// Allow the iframe to actually submit
paymentWidget.SubmitOk();
},
OnSuccess: function (response) {
clearTimeout(timeoutId);
hideLoadingSpinner();
showSuccess(response);
},
OnDecline: function (response) {
clearTimeout(timeoutId);
hideLoadingSpinner();
showDecline(response);
},
OnError: function (error) {
clearTimeout(timeoutId);
hideLoadingSpinner();
showError(error);
},
OnCancel: function () {
clearTimeout(timeoutId);
hideLoadingSpinner();
}
});
paymentWidget.Initialize(100.00, 0);
OnError is also fired for non-timeout conditions (PCI compliance violations, amount validation errors, server-side exceptions). When it fires, the detail argument is a string message — inspect it to decide whether the situation should be treated as a timeout or something else.
Displaying Timeout Messages to Users
User-Friendly Message Examples
- Friendly: "Sorry, this is taking longer than expected. Please wait a moment, or click below to try again."
- Clear: "The payment request timed out. Your card has NOT been charged. Please try submitting your payment again."
- Helpful: "We're having trouble connecting to our payment processor. Please check your internet connection and try again in a moment."
Example: Simple Timeout Message Display
function displayTimeoutMessage() {
hideLoadingSpinner();
var messageBox = document.getElementById('payment-message');
messageBox.innerHTML = `
<div class="timeout-message">
<h3>Payment Request Timed Out</h3>
<p>
We didn't receive a response from the payment processor
in time. We aren't sure yet whether the payment went through;
please wait a moment before trying again.
</p>
<p><strong>What you can do:</strong></p>
<ul>
<li>Wait a moment and try again</li>
<li>Check your internet connection</li>
<li>Contact support if the problem continues</li>
</ul>
<button onclick="retryPayment()" class="retry-button">Try Again</button>
</div>
`;
messageBox.style.display = 'block';
messageBox.scrollIntoView({ behavior: 'smooth' });
}
function retryPayment() {
document.getElementById('payment-message').style.display = 'none';
// The form is still live inside the iframe; the user can resubmit.
}
Example: Styled Timeout Message CSS
<style>
.timeout-message {
background-color: #fff3cd;
border: 2px solid #ffc107;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.timeout-message h3 { color: #856404; margin-top: 0; margin-bottom: 15px; }
.timeout-message p { color: #856404; line-height: 1.6; margin-bottom: 10px; }
.timeout-message ul { color: #856404; margin: 10px 0 20px 20px; }
.retry-button {
background-color: #0693e3;
color: white;
border: none;
padding: 12px 24px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.3s;
}
.retry-button:hover { background-color: #007cba; }
</style>
Complete Example: Full Timeout Handling
A complete working example combining the concepts above:
<!DOCTYPE html>
<html>
<head>
<title>Payment with Timeout Handling</title>
<script src="https://your-payment-domain.com/scripts/hpp.js"></script>
</head>
<body>
<div id="payment-container"></div>
<div id="payment-message" style="display:none;"></div>
<div id="loading-spinner" style="display:none;">Processing your payment...</div>
<script>
var PAYMENT_TIMEOUT_MS = 60000;
var timeoutTimer = null;
var paymentWidget = new hpp({
Target: "payment-container",
HPPUrl: "https://your-payment-domain.com/",
Token: "YOUR_TOKEN",
MerchantId: "YOUR_MERCHANT_ID",
ShowPaymentTypeCc: true,
InitialPaymentType: "CC",
OnSubmit: function (amount) {
document.getElementById('loading-spinner').style.display = 'block';
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(handleTimeout, PAYMENT_TIMEOUT_MS);
// Required — without SubmitOk() the iframe will not submit.
paymentWidget.SubmitOk();
},
OnSuccess: function (response) {
clearTimeout(timeoutTimer);
document.getElementById('loading-spinner').style.display = 'none';
showMessage('success', 'Payment Successful!',
'Your payment has been processed. Thank you!');
},
OnDecline: function (response) {
clearTimeout(timeoutTimer);
document.getElementById('loading-spinner').style.display = 'none';
showMessage('error', 'Payment Declined',
response.ResponseMessage || 'Please check your payment information and try again.');
},
OnError: function (errorMessage) {
clearTimeout(timeoutTimer);
document.getElementById('loading-spinner').style.display = 'none';
showMessage('error', 'Payment Error',
'An error occurred: ' + errorMessage);
},
OnCancel: function () {
clearTimeout(timeoutTimer);
document.getElementById('loading-spinner').style.display = 'none';
}
});
paymentWidget.Initialize(100.00, 0);
function handleTimeout() {
document.getElementById('loading-spinner').style.display = 'none';
var messageDiv = document.getElementById('payment-message');
messageDiv.innerHTML = `
<div class="message-box timeout">
<h3>Request Timed Out</h3>
<p>
The payment request took too long to complete.
We aren't sure yet whether the charge went through,
so please wait a moment before retrying.
</p>
<p><strong>What to do next:</strong></p>
<ul>
<li>Check your internet connection</li>
<li>Wait a moment for the system to settle</li>
<li>Verify with your server whether the transaction completed before retrying</li>
<li>Contact support if this keeps happening</li>
</ul>
</div>
`;
messageDiv.style.display = 'block';
messageDiv.scrollIntoView({ behavior: 'smooth' });
}
function showMessage(type, title, message) {
var messageDiv = document.getElementById('payment-message');
messageDiv.innerHTML =
'<div class="message-box ' + type + '">' +
'<h3>' + title + '</h3><p>' + message + '</p></div>';
messageDiv.style.display = 'block';
messageDiv.scrollIntoView({ behavior: 'smooth' });
}
</script>
</body>
</html>
Testing Timeout Scenarios
- Reduce the timeout in your code to something short (e.g., 5 seconds) during testing.
- Throttle the network with browser dev tools ("Slow 3G" in Chrome/Edge) to force slow responses.
- Submit a test payment and confirm your timeout message appears.
- Test the retry path after the message appears.
- Return the timeout to production value (typically 30–60 seconds) when finished.
Important Considerations
Don't Automatically Retry
Never auto-retry a payment after a timeout. The original request may have succeeded; an automatic retry can double-charge the customer.
function handleTimeout() {
submitPaymentAgain(); // dangerous — may double-charge
}
function handleTimeout() {
displayTimeoutMessage();
enableRetryButton(); // let the user choose
}
Verify State Server-Side Before Retrying
After a suspected timeout, the safest action is to ask your own backend whether the transaction completed — relying on the HPP's correlation ID to tie the original request to its eventual outcome.
function handleTimeout() {
displayTimeoutMessage();
// Optional: after a short delay, check with your server whether
// the transaction actually completed using the correlation ID
// that was passed to the HPP.
setTimeout(function () {
checkTransactionStatus(correlationId)
.then(function (status) {
if (status === 'completed') {
showMessage('success',
'Payment Completed',
'Your payment went through successfully.');
}
});
}, 5000);
}
Log Timeout Events
function handleTimeout() {
console.error('Payment timeout', {
timestamp: new Date().toISOString(),
merchantId: 'YOUR_MERCHANT_ID',
amount: currentAmount,
timeoutMs: PAYMENT_TIMEOUT_MS,
correlationId: correlationId
});
if (window.analytics) {
analytics.track('Payment Timeout', {
amount: currentAmount,
duration: PAYMENT_TIMEOUT_MS
});
}
displayTimeoutMessage();
}
Troubleshooting Common Issues
| Problem | Possible Cause | Solution |
|---|---|---|
| Frequent timeouts | Network issues or upstream processor latency | Check connectivity; contact Procare support with correlation IDs if the issue persists |
| Timeout message never appears | setTimeout not started, or the timer isn't being cleared in other callbacks correctly |
Verify the timer is started in OnSubmit (or Initialize) and cleared in every terminal callback |
| User charged twice after timeout | Automatic retry on timeout | Remove auto-retry; always require explicit user action and verify state server-side first |
| Timeout fires too eagerly | Timer too short for real-world processor latency | Use 30–60 seconds for payment submission |
Summary Checklist
- Set a parent-side timer of 30–60 seconds — the library does not provide one
- Start the timer in
OnSubmit(after callingSubmitOk()) or aroundInitializefor load timeouts - Clear the timer in
OnSuccess,OnDecline,OnError, andOnCancel - Display a clear, user-friendly timeout message
- Never auto-retry — require explicit user action
- Verify transaction state server-side before retrying
- Log timeouts with the correlation ID for later investigation
- Test your timeout UX with network throttling before going live