Embed a pay button or checkout

Take a payment on your own website with one script tag, using a button that opens the hosted checkout or a compact checkout drawn inside your page.

Every payment link you create has a hosted checkout at https://payments.cosmospay.lat/{id}. To take the payment without sending the payer away from your site, add one script and then one of two elements:

  • Pay button: <cosmos-pay-button> draws a button. Clicking it opens the hosted checkout in a popup window, and the button tells your page when the status changes.
  • Embedded checkout: <cosmos-pay-checkout> draws a compact checkout inside your page: the amount, the network, the QR code, an Open in wallet button and the live status.

Both take the payment link id: the id of the payment intent you created on your server (the last part of its checkout URL). Create the intent first (see Payment intents).

Add the script

<script src="https://payments.cosmospay.lat/embed/cosmos-pay.js" defer></script>

Once per page, anywhere in it. The elements are standard custom elements, so they work in plain HTML and in any framework. The script needs no API key, so never put your key in a web page.

Pay button

<cosmos-pay-button link="cmh4x2k9a0001l70g8v5q3z2w"></cosmos-pay-button>
  • The label is "Pay with Cosmos Pay", in the payer's language. Text inside the element replaces it: <cosmos-pay-button link="…">Pay order #1001</cosmos-pay-button>.
  • Clicking opens the hosted checkout in a 480×780 popup. If the browser blocks the popup, the same tab goes to the checkout instead.
  • While the popup is open, the button fires the events below each time the status changes.
  • To restyle it, target its part: cosmos-pay-button::part(button) { … }.

Embedded checkout

<cosmos-pay-checkout link="cmh4x2k9a0001l70g8v5q3z2w" lang="es" theme="auto"></cosmos-pay-checkout>

It shows the amount and asset (or "Any amount" when the payer chooses), the link's description, the network, the QR code, Open in wallet, the status, and the expiry when the link has one. It reads the link from the payer's browser and keeps it current (every 5 seconds while it awaits payment, every 3 while the network confirms it, every 30 once it has expired), and stops when the payment is paid, failed or cancelled.

  • It is up to 420px wide by default. Override that with CSS on the element: cosmos-pay-checkout { max-width: none; }.
  • It is not an iframe: the payment pages refuse to be framed, on purpose. The element draws its own interface in a shadow root, so your page's styles do not leak into it.
  • Show one per payment. Each one reads its link about 12 times a minute, and that read is limited per payer address.

Attributes

AttributeElementValuesDefault
linkbothThe payment link idRequired
langbothen, es, pt, fr, de, hi, zh (a regional tag such as es-AR works too)The payer's browser language, else en
themebothlight, dark, auto (follows the payer's system setting)light
demobothPresent to show a sample that can't be paid, with no link and no request (for showcases): the button opens the sample checkout; the embedded checkout adds a button that stands in for the payer: pressed, the sample confirms and turns to paid, firing its events; pressed again, it starts overOff

On the button, lang sets the label and, when you give it, the hosted checkout's language too. Without it, the checkout uses the payer's own language.

Events

Both elements fire these events on themselves. They bubble, so you can also listen on document.

EventWhendetail
cosmospay:statusThe payment's status changes (also once on the first read){ id, status }
cosmospay:paidThe status becomes SUCCEEDED{ id, status }

status is one of the API's values: PENDING, SUBMITTED, SUCCEEDED, FAILED, CANCELLED, EXPIRED. SUBMITTED means the payment was sent and the network is confirming it. EXPIRED is not final: a payment verified late can still move an expired link to SUCCEEDED.

const checkout = document.querySelector('cosmos-pay-checkout');

checkout.addEventListener('cosmospay:status', (event) => {
  console.log(event.detail.id, event.detail.status);
});

checkout.addEventListener('cosmospay:paid', async () => {
  // Update the page, then ask YOUR server whether the order is paid.
  const res = await fetch('/orders/1001/status');
  // …
});

Events are UI hints, not proof of payment

Any script on the page can fire the same events, and a payer can change the page. Use them to update what the payer sees, never to release goods. Confirm every payment on your server: through the webhook (PAYMENT_INTENT_SUCCEEDED) or by reading the intent with your key (client.paymentIntents.fetch(id), or GET /v1/payment-intents/{id} in the REST API).

The pay button only hears from the checkout while its popup is open, and only if the browser keeps the two windows connected. If your page sends Cross-Origin-Opener-Policy: same-origin, use same-origin-allow-popups instead, or the button receives no updates. When the popup is blocked and the checkout opens in the same tab, there are no events at all, but your server still gets the webhook.

Content Security Policy

If your site sends a Content-Security-Policy, allow:

DirectiveValueWhy
script-srchttps://payments.cosmospay.latThe script itself
connect-srchttps://api.cosmospay.latThe embedded checkout reads its link from the API
img-srcdata:The QR code is a data URL

The elements need no inline styles or scripts. The script is updated in place with each release, so it is published without a fixed integrity hash.

Next steps

On this page