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.
<!-- 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
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.
<!-- 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>
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().
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.
<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-checkoutflagrequiredMarks 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 colorTints the Pay button to match your app. Accepts #rgb,
#rgba, #rrggbb, or #rrggbbaa.
Anything else is dropped.
data-merida-emailstringPrefill buyer email. Overrides the empty input on mount.
data-merida-namestringPrefill cardholder name.
data-merida-external-customer-idstringYour platform's user ID. Echoed back on every webhook so you can correlate without querying us.
data-merida-client-reference-idstringFree-form reference (order ID, quote ID). Also echoed on webhooks.
data-merida-discount-codestringPre-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 colorPay button tint. Hex only, validated before it's forwarded.
prefill.emailstringBuyer email.
prefill.namestringCardholder name.
prefill.externalCustomerIdstringYour user ID.
prefill.clientReferenceIdstringFree-form order reference.
prefill.discountCodestringDiscount code.
onLoaded(detail) => voidCalled 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:
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")
})
loadedeventIframe finished loading, spinner removed.
confirmedeventBuyer pressed Pay and payment is being processed. Close is temporarily blocked.
successeventPayment completed. Detail includes successURL and a
redirect boolean. If true, the iframe will navigate the
parent. If false, you handle success in-page.
closeeventBuyer dismissed via the × button. Backdrop clicks never fire this.
Instance methods
.close()() => voidTear down the overlay programmatically (e.g. after a success toast in your own UI).
.addEventListener(event, handler)functionStandard 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.
Next up
Hosted checkout link →