Skip to main content

Documentation Index

Fetch the complete documentation index at: https://polar.sh/docs/llms.txt

Use this file to discover all available pages before exploring further.

Let your customers securely add a payment method without leaving your site.

Getting a session token

Every embed is authenticated with a short-lived customer session token. Create it on your server to keep the Polar access token secret, then hand the token to the client.
import { Polar } from "@polar-sh/sdk";

const polar = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN });

const session = await polar.customerSessions.create({
  customerId: "the-customer-id",
});
The token expires after one hour and is scoped to that single customer.

Implementation

Start by installing the SDK.
npm install @polar-sh/checkout
PolarEmbedPaymentMethod.create() opens the embed as a full-screen modal overlay.
import { PolarEmbedPaymentMethod } from "@polar-sh/checkout/payment-method";

const embed = await PolarEmbedPaymentMethod.create({
  sessionToken: session.token,
});

embed.addEventListener("success", (event) => {
  console.log({event});
});
create() options:
OptionTypeDefaultDescription
sessionTokenstringRequired. Customer session token.
theme'light' | 'dark'lightColour scheme.
setAsDefaultbooleantrueWhether the new card should become the customer’s default payment method.
returnUrlstringcurrent URLWhere to return the customer after a redirect-based payment method (Amazon Pay, Klarna). Defaults to window.location.href.
localestring'en'BCP47 locale for the embed UI and Stripe Elements (e.g. 'en', 'fr-FR'). Unsupported locales fall back to English.
onLoaded(event: CustomEvent) => voidConvenience callback for the loaded event. Equivalent to embed.addEventListener('loaded', …).
Open the modal from a Client Component event handler:
"use client";
import { PolarEmbedPaymentMethod } from "@polar-sh/checkout/payment-method";

interface Props {
  sessionToken: string;
}

export function AddPaymentMethodButton({sessionToken}: Props) {
  const onClick = async () => {
    const embed = await PolarEmbedPaymentMethod.create({ sessionToken });
    embed.addEventListener("success", (event) => {
      console.log({event});
    });
  };

  return <button onClick={onClick}>Add payment method</button>;
}

Inline embed (vanilla JS)

Mount a chrome-less, auto-resizing iframe into an element you control:
import { PolarEmbedPaymentMethod } from "@polar-sh/checkout/payment-method";

const embed = PolarEmbedPaymentMethod.createInline({
  sessionToken: session.token,
  element: document.getElementById("payment-method")!,
});

embed.addEventListener("success", (event) => {
  console.log({event});
});
createInline() accepts the same options as create() (except returnUrl) plus a required element (the container to mount into).

Inline embed (React)

Use the <PolarPaymentMethod /> component:
import { PolarPaymentMethod } from "@polar-sh/checkout/react/payment-method";

return (
  <PolarPaymentMethod
    sessionToken={session.token}
    onSuccess={(paymentMethodId) => console.log({paymentMethodId})}
  />
);

Code Snippet

The simplest integration: add the script and a trigger element with data-polar-payment-method. Clicking the element opens the modal.
<button data-polar-payment-method="polar_cst_xxx">Add payment method</button>

<script
  defer
  data-auto-init
  src="https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js"
></script>
The same script also powers embedded checkout triggers — one tag covers every Polar embed.
AttributeValueDescription
data-polar-payment-methodstringRequired. The session token. Clicking the element opens the modal.
data-polar-payment-method-themelight | darkOptional theme override.
data-polar-payment-method-set-as-defaulttrue | falseOptional. Default true. Pass "false" to add the card without overriding the existing default.
data-polar-payment-method-return-urlstringOptional. Return URL for redirect-based payment methods. Defaults to the current page.
data-polar-payment-method-localestringOptional. BCP47 locale (e.g. 'en', 'fr-FR'). Unsupported locales fall back to English.

Localization

The embed is fully localized, pass a BCP47 code via the locale option (or data-polar-payment-method-locale attribute):
const embed = await PolarEmbedPaymentMethod.create({
  sessionToken: session.token,
  locale: "fr-FR",
});
When omitted, the embed defaults to English. Unsupported locales also fall back to English. See Localization for the full list of supported languages.

Events

All events are dispatched as cancelable CustomEvents on the embed instance. Call event.preventDefault() to opt out of the SDK’s default action.
EventDetailDefault action
loadedRemoves the loader spinner once the iframe is ready.
closeTears down the iframe (unless locked by a pending confirmed).
confirmedMarks the modal as non-closable while Stripe is processing.
success{ paymentMethodId: string }Auto-closes the modal. Call preventDefault() to keep it open.
error{ code: 'invalid_request' | 'unauthorized' | 'processing_failed' | 'unknown' }Re-enables closing the modal after a failure.

Redirect-based payment methods

Some payment methods authorise on the provider’s own site. The browser navigates the whole tab away and back to returnUrl (defaults to the page the SDK was opened from), so the modal can’t survive the round-trip. Read the outcome on the returned page with the static getRedirectResult():
import { PolarEmbedPaymentMethod } from "@polar-sh/checkout/payment-method";

const result = PolarEmbedPaymentMethod.getRedirectResult();
// result: { status: 'succeeded' | 'failed' } | null

if (result?.status === "succeeded") {
  // refresh the customer's payment methods
}
In React, use the usePaymentMethodRedirectResult hook to avoid writing your own effect:
import { usePaymentMethodRedirectResult } from "@polar-sh/checkout/react/payment-method";

usePaymentMethodRedirectResult({
  onSuccess: () => console.log("Payment method added"),
  onError: () => console.error("Could not add payment method"),
});
Either way, the status query param is stripped from the URL so a refresh won’t surface a stale result. Card payments (3DS) complete inside the modal and never trigger this path.