Paycose Docs
Checkout SDK

Embed & Vanilla API

The no-bundler path — the CheckoutSDK script-tag global, the jQuery plugin, and the 3DS return contract.

The no-bundler path

Two IIFE builds are published for pages that have no build step. Each one attaches a single global and needs nothing else:

BuildGlobal it definesWhat it gives you
vanilla.jswindow.CheckoutSDKmountEmbeddedCheckout, registerProvider, getAvailableProviders
jquery.jswindow.CheckoutJQuerythe same three, plus $.checkout, $.fn.checkout() and $.fn.checkoutDestroy()

Both mount the cross-origin checkout frame. Card data is entered inside an iframe served from the Lodash frame origin, so raw card numbers never reach your page's DOM or JavaScript.

styles.css is not auto-injected. Load it yourself, or the frame container renders unstyled.

<link rel="stylesheet" href="https://cdn.example.com/checkout-sdk/styles.css" />
<script src="https://cdn.example.com/checkout-sdk/vanilla.js"></script>

CheckoutSDK global

The vanilla build exposes exactly three functions — nothing more.

Prop

Type

Full working example

<link rel="stylesheet" href="https://cdn.example.com/checkout-sdk/styles.css" />
<script src="https://cdn.example.com/checkout-sdk/vanilla.js"></script>

<div id="checkout"></div>
<button id="cancel">Cancel</button>

<script>
  var handle = CheckoutSDK.mountEmbeddedCheckout('#checkout', {
    source: { kind: 'checkout', session: 'cs_live_9f2c...' },
    frameUrl: 'https://pay.example.com/embed',
    config: {
      apiEndpoint: 'https://api.example.com',
      publishableKey: 'pk_live_9c1f4b2ad8',
    },
    returnUrl: window.location.href,
    onSuccess: function () {
      window.location.href = '/thank-you';
    },
    onError: function (err) {
      console.error('[checkout]', err.message);
    },
    onClose: function () {
      console.log('[checkout] customer closed the frame');
    },
  });

  document.getElementById('cancel').addEventListener('click', function () {
    handle.destroy();
  });
</script>

handle.destroy() closes the frame, removes SDK-created DOM, and detaches the 3DS-return message listener. Calling it twice is a no-op.

Options

Prop

Type

config fields:

Prop

Type

frameUrl is a base, not a page. The SDK builds the real page URL itself. Pass https://pay.example.com/embed — passing https://pay.example.com/embed/checkout.html is tolerated (checkoutFrameUrl() is idempotent) but is not the supported contract.

Key format is enforced client-side. publishableKey must be pk_test_ or pk_live_ followed by at least four alphanumeric characters. A bare prefix, an underscore inside the body, an sk_ secret key, an Omise pkey_ key, a pk_p256_ service-account key, and an over-long provider key (Stripe shares the pk_test_/pk_live_ prefix) are all rejected with PublishableKeyError before any request is made.

Since the vanilla global does not expose configure(), pass apiEndpoint, publishableKey and frameUrl at every call site.


jQuery

Load jquery.js and the plugin installs itself onto window.jQuery and window.$. It does not care about script order: if jQuery is not present yet, the build retries on DOMContentLoaded and on load.

It installs three things:

Prop

Type

window.CheckoutJQuery is also set, holding the same three static functions plus install(jQuery) for the rare case where you need to attach the plugin to a jQuery instance the build cannot see (a module-scoped copy, or a jQuery.noConflict(true) result).

<link rel="stylesheet" href="https://cdn.example.com/checkout-sdk/styles.css" />
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.example.com/checkout-sdk/jquery.js"></script>

<div id="checkout"></div>
<button id="cancel">Cancel</button>

<script>
  $(function () {
    $('#checkout').checkout({
      source: { kind: 'checkout', session: 'cs_live_9f2c...' },
      frameUrl: 'https://pay.example.com/embed',
      config: {
        apiEndpoint: 'https://api.example.com',
        publishableKey: 'pk_live_9c1f4b2ad8',
      },
      returnUrl: window.location.href,
      onSuccess: function () { window.location.href = '/thank-you'; },
      onError: function (e) { console.error(e.message); },
    });

    $('#cancel').on('click', function () {
      $('#checkout').checkoutDestroy();
    });
  });
</script>

Calling .checkout() again on the same element destroys the previous instance first, so re-mounting after a cart change is safe and never leaks a frame or a message listener:

function remount(session) {
  $('#checkout').checkout({
    source: { kind: 'checkout', session: session },
    frameUrl: 'https://pay.example.com/embed',
    config: { apiEndpoint: 'https://api.example.com', publishableKey: 'pk_live_9c1f4b2ad8' },
  });
}

The static namespace is the escape hatch when you want the raw handle instead of the chainable plugin:

var handle = $.checkout.mountEmbeddedCheckout('#checkout', options);
handle.destroy();

3DS and redirects

Off-site steps — 3DS challenges, bank apps, redirect-based methods — take the customer away from your page and back again. The contract has two halves.

Leaving: do not pass onRedirect

Omitting onRedirect is the correct default. The SDK navigates the top window itself via its internal navigateTop, which is written to survive being called from inside a cross-origin iframe.

// Correct — the SDK owns the navigation
CheckoutSDK.mountEmbeddedCheckout('#checkout', { source: source, config: config, frameUrl: frameUrl });

If you do pass a handler, you own the navigation from that point on. A handler that only logs strands the customer mid-3DS on a blank frame:

// Only if you genuinely need to route it yourself — you must navigate
CheckoutSDK.mountEmbeddedCheckout('#checkout', {
  source: source,
  config: config,
  frameUrl: frameUrl,
  onRedirect: function (url) {
    myAnalytics.track('3ds_redirect');
    window.top.location.href = url; // required
  },
});

Use top.location.href = url, never window.top.location.assign(url). A cross-origin Location object exposes only the href setter — calling assign() throws a SecurityError.

Coming back: checkout-return.html

The provider returns the customer to checkout-return.html on the frame origin — the only other page the frame serves besides checkout.html. That page posts a message back to the opener:

{ type: 'lodash-sdk:3ds-return', query: '?payment_intent=pi_…' }

mountEmbeddedCheckout already installs the message listener for you, and it ignores any message whose origin is not the frame origin. On receipt it re-polls payment status (10 attempts, 3 s apart) and then fires onSuccess — or onError once all ten attempts are exhausted without a paid status. You do not have to write any of this.

If you are hosting the return page yourself, or need to recognise the message in your own listener, the constant and the type guard are exported from the ESM entry point:

import { CHECKOUT_RETURN_MESSAGE_TYPE, isCheckoutReturnMessage, buildCheckoutReturnUrl } from '@checkout/sdk'

window.addEventListener('message', (ev) => {
  if (ev.origin !== FRAME_ORIGIN) return
  if (!isCheckoutReturnMessage(ev.data)) return
  console.log(CHECKOUT_RETURN_MESSAGE_TYPE, ev.data.query)
})

buildCheckoutReturnUrl(frameHref) derives the return URL from a frame href, preserving its query string and dropping the hash.


Custom providers

registerProvider / getAvailableProviders are the same registry the bundled entry points use, exposed on both globals:

CheckoutSDK.getAvailableProviders(); // ['lodash']

CheckoutSDK.registerProvider('acme', function (config) {
  return new AcmeProvider(config); // config: { apiEndpoint, vaultEndpoint?, publishableKey?, metadata? }
});

CheckoutSDK.getAvailableProviders(); // ['lodash', 'acme']

Register before mounting. See Payment providers for the interface a factory must return.


On this page