Iframe
Keep customers on your checkout by opening the application in a full-screen iframe.
Instead of redirecting, open the application url from Create order in an iframe that covers the page. The application talks to your page with window.postMessage, so you know when to close it and what happened.
Messages are for the UI, postbacks are for orders
Use iframe messages to close the iframe and move the customer along. Use postbacks to trigger server-side reconciliation. Fulfill only after reading authenticated current status and matching the saved order details. A browser message can be faked by anything running on the page.
Try it
Click the button to open the application in an iframe, then pick an outcome. The log shows each message your page receives and what the example code below does with it. Switch to Real staging application and paste the url from a staging Create order call to watch the real thing.
The simulator sends sample browser messages. Server-side reconciliation is still required for a real order.
- Open the application and pick an outcome.
Messages
Messages come from these origins:
| Environment | Origin |
|---|---|
| Staging | https://consumer-staging.paytomorrow.com |
| Production | https://consumer.paytomorrow.com |
Most messages are a plain string in event.data. Two events also send a second message with an object that carries details.
| Message | When it's sent | What to do |
|---|---|---|
pt-close | The customer closed the application with the X, or cancelled. Closing may abandon the application depending on the flow; read the server-side status before updating the order. | Close the iframe and return to checkout. |
pt-confirmation | The application is complete and in Ready to Settle. Also sent as an object, see below. | Close the iframe and show processing while your server confirms the order. |
pt-finished | The customer clicked Close on the success screen. Not guaranteed, since customers can just leave. | Close the iframe. Don't depend on this message. |
pt-decline | The customer was declined after a lender was selected. | Close the iframe and offer another payment method. |
pt-error | Something went wrong, or the customer was declined before a lender was selected. | Close the iframe and offer another payment method. |
pt-pre-approval | A pre-approval was approved. Also sent as an object, see below. | Show the approved amount. |
Object messages
{
"message": "pt-confirmation",
"notifyUrl": "https://yourstore.com/webhooks/paytomorrow",
"lender": "paytomorrow",
"comment": "",
"taxExempt": false
}Example
A complete, dependency-free implementation. Your server creates the order and passes the url to the page.
<div class="pt-iframe-container" hidden>
<iframe id="pt-iframe" title="PayTomorrow financing application"></iframe>
</div>
<style>
/* Keep both transparent. The application draws its own dimmed backdrop over your page. */
.pt-iframe-container {
position: fixed;
inset: 0;
z-index: 9999;
}
.pt-iframe-container > iframe {
width: 100%;
height: 100%;
border: 0;
background: transparent;
color-scheme: normal;
}
</style>
<script>
// Only accept messages from PayTomorrow. Compare exact origins, never a regex or substring.
const PAYTOMORROW_ORIGINS = new Set([
'https://consumer.paytomorrow.com',
'https://consumer-staging.paytomorrow.com', // remove in production
]);
const container = document.querySelector('.pt-iframe-container');
const iframe = document.getElementById('pt-iframe');
function openPayTomorrow(applicationUrl) {
iframe.src = applicationUrl;
container.hidden = false;
document.body.style.overflow = 'hidden';
}
function closePayTomorrow() {
container.hidden = true;
iframe.src = 'about:blank';
document.body.style.overflow = '';
}
window.addEventListener('message', (event) => {
if (!PAYTOMORROW_ORIGINS.has(event.origin) || event.source !== iframe.contentWindow) return;
// Some events arrive twice: once as a string, once as an object.
const type = typeof event.data === 'string' ? event.data : event.data?.message;
switch (type) {
case 'pt-confirmation':
closePayTomorrow();
// Show a "processing" state. Your server reconciles authenticated status after a postback.
window.location.assign('/checkout/paytomorrow/success');
break;
case 'pt-close':
case 'pt-finished':
closePayTomorrow();
break;
case 'pt-decline':
case 'pt-error':
closePayTomorrow();
window.location.assign('/checkout?payment=declined');
break;
}
});
// `applicationUrl` comes from your server's Create order call.
openPayTomorrow(applicationUrl);
</script>A check like event.origin.match("paytomorrow.com"), or a regex written inside a JavaScript string, can match lookalike domains such as ecomxpaytomorrow.com. Anyone who registers one of those could send your page fake pt-confirmation messages. A Set of exact origins avoids that. Older versions of this guide used a regex; update your integration if you copied it.
Check your site's Content-Security-Policy. frame-src (or child-src) must allow https://consumer.paytomorrow.com and, in staging, https://consumer-staging.paytomorrow.com. Lenders may also open their own pages inside the application, so if you use a strict CSP, test the full flow in staging.
Set overflow: hidden on body while the iframe is open, as the example does, and make sure the container is position: fixed with inset: 0.