Checkout

Embedded overlay

Open the checkout in an iframe on top of your page. One SDK call plus one anchor, no server work, no redirect.

How it works

The script scans the page for two kinds of trigger and intercepts clicks on them: anchors with the merida-button class (the no-server "buy link" pattern below), and anchors with the data-merida-checkout attribute (the integrator-minted pattern where you pre-call POST /checkouts). On click, it mounts a full-viewport backdrop with a centred iframe pointing at the buyer's checkout. The iframe detects ?embed=true and swaps to a compact layout (no header, sticky pay footer). Buyers dismiss via the explicit × button. Clicking the dark backdrop does nothing, so a mid-payment click can't lose the form.

Buy link (no server code)

The simplest way to sell. Paste two lines into any page: a permanent buy URL for the product, plus the embed script. Each click mints a fresh single-use checkout server-side and opens it in the overlay.

html
<!-- Drop into your page anywhere -->
<a class="merida-button" href="https://pay.meridapay.com/pay/<product-id>">
  Buy
</a>

<!-- Drop the script once per page -->
<script src="https://pay.meridapay.com/embed.js" defer></script>

Get the snippet pre-filled from the dashboard: open the product, click Sell this product → Overlay tab → Copy.

Style the <a> however you want. Only the merida-button class matters: embed.js looks for it (or [data-merida-checkout]) and binds the click handler. No data attributes required.

Install the SDK

bash
bun add @meridapay/sdk

The browser-only loader lives at the /embed subpath. It lazy-injects pay.meridapay.com/embed.js on first call, then hands you a typed handle. No <script> tag for you to wire by hand, no global pollution beyond what the bundle itself sets.

Declarative usage

Mint the checkout URL server-side (see Hosted checkout link), interpolate it into the anchor's href, and call attach() once per page to scan the DOM.

html
<!-- href value comes from POST /checkouts response -->
<a href="https://pay.meridapay.com/<token>"
   data-merida-checkout
   data-merida-checkout-theme="light"
   data-merida-brand-color="#214fd8">
  Subscribe
</a>
javascript
import { embed } from "@meridapay/sdk/embed"

const merida = await embed()
merida.attach()

attach() is safe to call multiple times. Re-scans are idempotent. Call it again after rendering new anchors to bind them.

Programmatic usage

When the buyer identity lives in your session, not your markup, mint the URL on demand and call create().

javascript
import { embed } from "@meridapay/sdk/embed"

// Fetch a fresh checkout URL from your backend, then open the overlay:
const { url } = await fetch("/api/checkout", { method: "POST" }).then(r => r.json())

const merida = await embed()
const handle = merida.create(url, {
  theme: "light",
  brandColor: "#214fd8",
  prefill: {
    email: "[email protected]",
    name: "Jane Doe",
  },
  onLoaded: (detail) => console.log("checkout ready", detail),
})

handle.addEventListener("success", () => { /* paid */ })

Without the SDK (script tag)

If you can't bundle JavaScript on the page, drop the script tag directly. It self-initializes via data-auto-init and scans the page for [data-merida-checkout] anchors.

html
<a href="https://pay.meridapay.com/<token>" data-merida-checkout>Subscribe</a>

<script src="https://pay.meridapay.com/embed.js" data-auto-init defer></script>

The behaviour is identical to the SDK pattern. Use whichever fits your build setup.

Data attributes

data-merida-checkoutflagrequired

Marks the anchor as a checkout trigger. Presence is enough. The value is ignored.

data-merida-checkout-theme"light" | "dark"

Theme hint forwarded as ?theme= on the iframe URL.

data-merida-brand-colorhex color

Tints the Pay button to match your app. Accepts #rgb, #rgba, #rrggbb, or #rrggbbaa. Anything else is dropped.

data-merida-emailstring

Prefill buyer email. Overrides the empty input on mount.

data-merida-namestring

Prefill cardholder name.

data-merida-external-customer-idstring

Your platform's user ID. Echoed back on every webhook so you can correlate without querying us.

data-merida-client-reference-idstring

Free-form reference (order ID, quote ID). Also echoed on webhooks.

data-merida-discount-codestring

Pre-applied discount code, same behaviour as the user pasting it manually on the checkout form.

Programmatic options

Same shape as the data attributes, but in a JS object.

theme"light" | "dark"

Theme hint.

brandColorhex color

Pay button tint. Hex only, validated before it's forwarded.

prefill.emailstring

Buyer email.

prefill.namestring

Cardholder name.

prefill.externalCustomerIdstring

Your user ID.

prefill.clientReferenceIdstring

Free-form order reference.

prefill.discountCodestring

Discount code.

onLoaded(detail) => void

Called once when the iframe reports loaded. Equivalent to instance.addEventListener("loaded", …).

Events

The returned instance is an EventTarget. Listen for the lifecycle you care about:

javascript
const instance = window.Merida.EmbedCheckout.create(url, { theme: "light" })

instance.addEventListener("loaded", () => {
  // iframe mounted, spinner gone
})

instance.addEventListener("confirmed", () => {
  // buyer clicked Pay; close button is suppressed while payment processes
})

instance.addEventListener("success", (detail) => {
  // payment complete; detail.redirect === true means the iframe will
  // navigate the parent to detail.successURL. Set your own logic here
  // if you'd rather stay on-page (e.g. confetti + close).
})

instance.addEventListener("close", () => {
  // user dismissed via the × button (backdrop clicks are inert)
  analytics.track("checkout_dismissed")
})
loadedevent

Iframe finished loading, spinner removed.

confirmedevent

Buyer pressed Pay and payment is being processed. Close is temporarily blocked.

successevent

Payment completed. Detail includes successURL and a redirect boolean. If true, the iframe will navigate the parent. If false, you handle success in-page.

closeevent

Buyer dismissed via the × button. Backdrop clicks never fire this.

Instance methods

.close()() => void

Tear down the overlay programmatically (e.g. after a success toast in your own UI).

.addEventListener(event, handler)function

Standard EventTarget API.

Security notes

brandColor is validated with a strict hex regex both in embed.js and again on the checkout page, so a hand-edited URL can't inject CSS. Prefill values ride on the URL and are visible to anyone with access to the browser. Don't put secrets there.