@alguna/checkout-sdk
Embed Alguna Checkout in your product. Your server mints a checkout session with your secret API key; your frontend mounts the component with the returned URL; Alguna handles payment collection, tax, and account/subscription creation — the same trust model as Stripe's embedded Checkout.
The checkout renders inside an iframe served from Alguna's origin. This is a
hard isolation boundary: your page's CSS can't reach into the checkout, and the
checkout can't restyle your page. Card entry uses Stripe's own iframe (so your
site stays out of PCI scope). Style the checkout only through the
appearance prop — the same model as Stripe Elements. The SDK ships no
stylesheet; there's nothing to import.
npm install @alguna/checkout-sdk
Quickstart
0. One-time setup (Alguna dashboard)
- Create an API key: Settings → API keys (format
id.secret— server secret, never ship it to a browser). - Have at least one payment processor connected (e.g. Stripe) for the legal entity you bill from.
1. Decide who the buyer is (three identity modes)
| Your situation | What to send on session create | What checkout does |
|---|---|---|
| Buyer is an existing Alguna customer | customer_id (or customer_alias) | Charges that customer; can show their saved cards (see Saved cards) |
| Buyer is logged into your product | customer: { name, email, address? … } | Creates the Alguna customer from what you already know — no re-typing |
| Buyer is anonymous (public pricing page) | (nothing) | Checkout collects email/name itself before payment |
2. Create a session (your server)
const response = await fetch("https://api.alguna.io/checkout-sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ALGUNA_API_KEY}`,
"Alguna-Version": "2026-04-01", // pin the API version
"Content-Type": "application/json",
},
body: JSON.stringify({
customer: { name: "Acme Corp", email: "[email protected]" }, // mode 2
plan_id: "plan_...", // or line_items: [...] / amount
client_reference_id: "order_1234", // echoed on the webhook
expires_in_seconds: 3600,
}),
});
const session = await response.json();
// → { id, url, session_token, amount, currency, expires_at, ... }
3. Mount the checkout (your frontend)
React:
import { AlgunaCheckout } from "@alguna/checkout-sdk/react";
<AlgunaCheckout
url={session.url}
layout="compact" // your page provides the context
appearance={{ accentColor: "#4d68eb" }}
onComplete={({ checkoutSessionId }) => showSuccess()}
onError={({ code }) => {
if (code === "session_expired") refreshSession(); // mint a new one, re-mount
}}
/>;
Vanilla:
import { AlgunaCheckout } from "@alguna/checkout-sdk";
const checkout = AlgunaCheckout.init({
url: session.url,
container: "#checkout",
onComplete: () => (window.location.href = "/welcome"),
});
// checkout.update({ appearance }); checkout.destroy();
onComplete fires the moment payment succeeds — replace or unmount the
component and take the customer forward; the checkout shows only a brief
neutral confirmation.
4. React to completion (your server)
Subscribe to the checkout.session.completed webhook — its payload carries
accountId, subscriptionId, paymentId, invoiceId, and your
clientReferenceId — or poll GET /checkout-sessions/{id} until
payment_id/subscription_id appear.
Appearance
Everything applies live (no re-mount) over your organization's dashboard branding:
appearance={{
preset: "soft", // or "minimal" | "sharp" — a curated
// starting point; every field below overrides it
accentColor: "#4f46e5", // CTAs & highlights
primaryColor: "#0f0f0f",
mode: "dark", // dark canvas
borderRadius: 12, // cards, inputs, buttons
fontFamily: "Georgia, serif",// no font download — use stacks you load
density: "compact", // tighter vertical rhythm
payButtonLabel: "Subscribe for {amount}",
colors: { // fine-grained palette
surface: "#0b1220", card: "#111a2e", text: "#e7e5e4",
mutedText: "#a8a29e", border: "#1f2b45", inputBackground: "#0e1626",
inputBorder: "#2563eb", // input outlines, separate from `border` (the
// frame) — set `border` to the surface color to
// flatten the frame while keeping fields outlined
},
}}
Component sizing: layout: "compact" drops the checkout's own heading for
in-page embedding. The iframe auto-resizes to its content; pass maxHeight to
cap it, and the form scrolls inside the component above that height.
Styling
The only supported way to style the checkout is the appearance prop — colors,
border radius, font family, light/dark mode, and curated presets. Because the
checkout is isolated in an iframe, your page's CSS cannot (and should not) target
it. This mirrors how Stripe Elements and Primer are styled.
Custom pay button
Render your own pay button instead of the built-in one:
const ref = useRef<AlgunaCheckoutRef>(null);
const [state, setState] = useState<CheckoutState>();
<AlgunaCheckout
ref={ref}
url={session.url}
hidePayButton
onCheckoutStateChange={setState}
onComplete={...}
/>
<button disabled={!state?.canSubmit} onClick={() => ref.current?.submit()}>
{state?.isProcessing ? "Processing…" : "Pay"}
</button>
hidePayButton hides our button (the "payments are secure and encrypted" line
stays); submit() on the ref triggers payment; onCheckoutStateChange reports
{ canSubmit, isProcessing, isComplete, error } so you can drive your button.
Saved cards
By default every buyer — new or returning — enters their card fresh. To let a
returning customer pay with a card they've used before, pass
showSavedPaymentMethods (only meaningful for customer_id / customer_alias
sessions, where we know who the buyer is):
<AlgunaCheckout url={session.url} showSavedPaymentMethods />
With it on, a customer who has a card on file sees it pre-selected, with a "use a different card" option to enter a new one. Cards are managed in your customer-facing settings, not edited inline at checkout.
Terms of service
The checkout shows a terms-acceptance checkbox that must be ticked before the buyer can pay. It links your legal entity's terms of service — the same terms configured for that entity elsewhere (quotes, hosted checkout) — so nothing needs to be passed on the SDK.
To make the checkbox optional (not block payment), pass
requireTermsAcceptance={false}:
<AlgunaCheckout url={session.url} requireTermsAcceptance={false} />
Trust line
The "Payments are secure and encrypted" line under the pay button can be hidden:
<AlgunaCheckout url={session.url} hidePaymentSecurityText />
Reacting to scroll
The checkout iframe is sized to its content, so wheel gestures over it are
forwarded to your page (which scrolls by default). To handle scrolling yourself
— e.g. inside a custom scroll container — use onScroll:
<AlgunaCheckout url={session.url} onScroll={(deltaY) => myScroller.scrollBy(0, deltaY)} />
It mirrors onResize.
Sessions & errors
- Sessions expire (
expires_in_seconds, default 24h, max 7d). Create one per checkout attempt. onErrorcodes:session_expired(mint a fresh session and re-mount) andload_failed(retry / support).
Security model
The checkout runs in an iframe from Alguna's origin, so your page never touches sensitive data or the checkout's DOM:
- Session tokens are short-lived and single-purpose. Each is minted by your
server (with your secret key) for one checkout, scoped to that session, and
expires (
expires_in_seconds). The browser only ever holds this scoped token, never your API key. - Card data never enters your DOM. It's captured inside Stripe's iframe (nested within the checkout iframe), so your site stays in PCI SAQ-A scope.
- Your CSS can't reach the checkout, and vice versa. The iframe is a hard
boundary — style only through
appearance. - Origin allowlisting. Orgs can restrict which sites may embed their checkout (dashboard setting); the embed refuses to render on a disallowed origin. The SDK passes your page's origin so the checkout can scope every postMessage to it.