`@mayarin/embed` adds one element, `<mayarin-checkout>`, that frames the hosted checkout inside your page. Use it when you have a plain HTML page, a CMS or a site builder, and you want the buyer to pay without leaving. There is no framework, no build step and no secret on the page.

```html
<script type="module" src="https://your-host/mayarin-embed.js"></script>

<mayarin-checkout
  base-url="https://api.mayarin.xyz"
  link="plk_01J8Z3K4M5N6P7Q8R9S0T1U2V3"
></mayarin-checkout>
```

Which form fits you\:

| You have                            | Use                                                                                         |
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
| A page and a payment link           | The element in `link` mode (this page)                                                      |
| A page and a fixed cart, no backend | The element in `cart` mode with a publishable key (this page)                               |
| A backend that mints the payment    | [`openCheckout`](/sdk/typescript/browser#open-the-checkout-in-a-modal), a modal with events |
| A React app                         | [`PayButton`](/sdk/typescript/react)                                                        |
| A React Native app                  | [`MayarinCheckoutModal`](/sdk/typescript/react-native)                                      |

## 1. Get the script

The element is a static file with no dependencies. Build it once and host it anywhere you serve scripts from\:

```bash
bun run --cwd packages/embed build
# packages/embed/dist/index.js  →  upload it as mayarin-embed.js
```

A bundler user can skip the file\: `import "@mayarin/embed"` registers the element, and `import { register } from "@mayarin/embed"` lets you pick the tag name or register lazily.

## 2. Link mode

Create a [payment link](/sdk/typescript/commerce#payment-links) in the dashboard or with the SDK, and put its id on the element. The buyer sees the link's checkout inside the frame.

```html
<mayarin-checkout
  base-url="https://api.mayarin.xyz"
  link="plk_01J8Z3K4M5N6P7Q8R9S0T1U2V3"
  height="720"
></mayarin-checkout>
```

No key is involved. A link id is an unguessable ULID, the same posture the hosted checkout page takes. A link of kind `open` has no price, so the frame opens on an amount field\: use it for tips and donations.

## 3. Cart mode

When you have no payment link, the element can mint the payment itself from cart lines. It uses a **publishable key** (`pk_…`), which can only create payments for your own merchant and can do nothing else.

```html
<mayarin-checkout
  base-url="https://api.mayarin.xyz"
  publishable-key="pk_live_..."
  cart='{
    "currency": "IDR",
    "lines": [{ "productId": "prd_...", "quantity": 1 }],
    "payment": { "asset": "USDC", "chain": "base" }
  }'
></mayarin-checkout>
```

The element sends two requests, in order, the same pair the hosted link page sends\:

1. `POST /v1/carts/checkout` creates the payment.
2. `POST /v1/payment-intents/:id/confirm` locks the price and allocates the deposit address.

Then it frames the pay page. While this runs the buyer sees "Menyiapkan pembayaran…".

Rules of cart mode\:

* **The cart names its rail.** `payment` (`{ asset, chain }`) is required, because the pay page shows a deposit to a rail already chosen and has no asset picker. To let the buyer choose, render one element per rail, or use a payment link.
* **The execution path defaults to `deposit-match`.** The pay page shows a QR to scan, and the contract path has nothing to scan. Set `executionPath` in the cart only if you know why.
* **The merchant is not yours to name.** The API records the key's own merchant, and a publishable key takes the name, city and country from your merchant profile whatever the cart says. A page cannot print another shop's name on the checkout.
* **The price is only as trusted as the page.** The amount comes from the browser, so a buyer can edit it. That is fine for donations, tips and open amounts. For a sale, mint on your backend ([server mode](/sdk/typescript/server)) or check the paid amount against your order in the webhook.
* **A secret key is refused.** `publishable-key` must start with `pk_`. The element shows an error instead of shipping a secret in markup.

## Attributes

| Attribute         | Mode | Required | Meaning                                                                           |
| ----------------- | ---- | -------- | --------------------------------------------------------------------------------- |
| `base-url`        | both | yes      | Origin of the payment API. A path prefix is kept; `javascript:` URLs are refused. |
| `link`            | link | yes      | The payment link id to check out.                                                 |
| `cart`            | cart | yes      | JSON object\: the cart body, including `payment`. Not valid together with `link`.  |
| `publishable-key` | cart | yes      | A `pk_…` key.                                                                     |
| `height`          | both | no       | Frame height. A bare number means pixels. Default `640px`.                        |

Changing an attribute re-renders the element, so a script can update `cart` when the buyer changes the quantity.

A missing or malformed attribute renders a visible one-line message in place of the checkout, such as `mayarin-checkout: the "link" attribute (or "cart" + "publishable-key") is required.` The message is for you wiring the embed\: a visible sentence gets fixed, a blank box ships.

## Isolation

The iframe is the boundary. The framed checkout is cross-origin to your page, so the browser enforces both directions\: your CSS cannot restyle the checkout, and the checkout cannot read your page. The shadow root only keeps the element's own sizing and error styling out of your cascade.

The frame is sandboxed to what the checkout needs\: scripts, forms, and popups so a wallet or explorer link opens in a working tab. Clipboard write is allowed so the buyer can copy the address and amount.

## Content Security Policy

On a page with a strict policy, allow\:

* `frame-src https://api.mayarin.xyz`, or your API origin, for the checkout frame.
* `script-src https://your-host`, wherever `mayarin-embed.js` lives, or a hash or nonce for it.
* `connect-src https://api.mayarin.xyz`, **cart mode only**, for the two requests above. Link mode makes no request of its own.

## Knowing the payment went through

The element has no events. It frames the checkout inline, and **your systems learn the outcome from the [webhook](/guides/webhooks)**, never from the page. If you need the page to react as soon as the buyer pays, open the checkout with [`openCheckout`](/sdk/typescript/browser#open-the-checkout-in-a-modal) instead\: it reports `onSuccess`, `onFailed` and `onClose`.

## Limits

* The element frames the checkout inline, not as a modal. For a modal over your page, use `openCheckout`.
* Cart mode is one rail per element.
* The hosted checkout page must stay frameable. If your API sits behind a security-headers layer, leave `/checkout/*` out of `X-Frame-Options` and `frame-ancestors`, or every embed goes blank at once.
