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.

Checkout
65-inch 4K Smart TV$1,149.97
Messages received
  1. Open the application and pick an outcome.

Messages

Messages come from these origins:

EnvironmentOrigin
Staginghttps://consumer-staging.paytomorrow.com
Productionhttps://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.

MessageWhen it's sentWhat to do
pt-closeThe 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-confirmationThe 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-finishedThe customer clicked Close on the success screen. Not guaranteed, since customers can just leave.Close the iframe. Don't depend on this message.
pt-declineThe customer was declined after a lender was selected.Close the iframe and offer another payment method.
pt-errorSomething went wrong, or the customer was declined before a lender was selected.Close the iframe and offer another payment method.
pt-pre-approvalA 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.

checkout.html
<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>

On this page