@emergente-labs/elements-sdk
v0.1.0
Published
Embeddable payment Elements SDK for moneymotion.
Readme
@emergente-labs/elements-sdk
Embeddable, processor-agnostic payment Elements for the browser. Mount Card, Express Checkout, or Address iframes on your page, collect a buyer's payment details, and run a full PaymentIntent confirmation — including 3-D Secure — without ever touching the card data yourself.
The SDK ships two interchangeable surfaces:
- Promise API (
@emergente-labs/elements-sdk) — a class-based facade for vanilla JS, React, Vue, etc. - Effect API (
@emergente-labs/elements-sdk/effect) — Effect-native primitives for codebases already running an Effect runtime.
Both surfaces share the same internals; the Promise wrapper is a thin layer.
Installation
npm install @emergente-labs/elements-sdk effect
# or
pnpm add @emergente-labs/elements-sdk effect
# or
yarn add @emergente-labs/elements-sdk effecteffect is a peer requirement — the SDK is built on top of it.
Quickstart
import { loadElements } from "@emergente-labs/elements-sdk";
// 1. Mint a PaymentIntent server-side, return its client_secret to the page.
// The SDK never sees your secret API key.
const elements = await loadElements({
publishableKey: "pk_test_...",
clientSecret: "cs_...",
iframeOrigin: "https://elements.example.com",
});
// 2. Mount the card element into your DOM.
const card = elements.create("payment");
card.mount("#card-container");
// 3. Listen for input state changes (validity, brand, etc.).
card.on("change", (event) => {
console.log(event.complete, event.brand);
});
// 4. Confirm when the buyer submits.
const result = await elements.confirm({
element: card,
billingAddress: {
firstName: "Ada",
lastName: "Lovelace",
lineOne: "1 Analytical Engine Way",
city: "London",
postalCode: "SW1A 1AA",
country: "GB",
},
returnUrl: window.location.href,
});
if (result.status === "succeeded") {
// Payment captured — redirect to confirmation page.
}3-D Secure (fingerprint + challenge) is handled automatically. The promise only resolves once the intent has reached a terminal status — your code does not need to manage redirects, hidden iframes, or ACS postMessages.
Effect API
import * as Elements from "@emergente-labs/elements-sdk/effect";
import { Effect, Stream } from "effect";
const program = Effect.gen(function* () {
const elements = yield* Elements.make({
publishableKey: "pk_test_...",
clientSecret: "cs_...",
iframeOrigin: "https://elements.example.com",
});
const card = yield* elements.create("payment");
yield* card.mount("#card-container");
yield* card.changes.pipe(
Stream.tap((event) => Effect.log(event)),
Stream.runDrain,
Effect.fork,
);
return yield* elements.confirm({
element: card,
billingAddress: { /* ... */ },
returnUrl: location.href,
});
});
Effect.runPromise(program.pipe(Effect.scoped));When the surrounding Scope closes, every iframe, listener, and fiber is torn
down automatically — no manual dispose() call is required.
Configuration
loadElements / Elements.make accept the following options:
| Option | Required | Description |
| ---------------- | -------- | --------------------------------------------------------------- |
| publishableKey | yes | Your pk_* publishable key. |
| clientSecret | yes | Client secret returned when you created the PaymentIntent. |
| iframeOrigin | yes | Origin serving the Elements iframe pages. |
| appearance | no | Theme + variables + rules forwarded to the iframe. |
| apiBase | no | Override the iframe's RPC base URL (defaults to iframeOrigin). |
Element types
| Type | Path | Purpose |
| ----------------- | ------------------- | ---------------------------------------- |
| payment | /v1/card.html | Card number / expiry / CVC. |
| expressCheckout | /v1/express.html | Apple Pay / Google Pay / wallet buttons. |
| address | /v1/address.html | Billing or shipping address. |
The iframe pages must be served by your backend at iframeOrigin. This SDK
handles the merchant-side glue only — see your provider's docs for hosting the
iframe pages themselves.
Errors
Every error raised by the Effect surface is a tagged class re-exported from
@emergente-labs/elements-sdk/errors. The Promise API re-throws them
verbatim, so instanceof checks work either way:
import { ElementsConfigError, ElementMountError } from "@emergente-labs/elements-sdk/errors";
try {
await loadElements({ /* ... */ });
} catch (err) {
if (err instanceof ElementsConfigError) {
// bad publishable key, missing window, etc.
}
}The full set: ElementsConfigError, ElementMountError,
ElementNotMountedError, ElementLoadError, UnsupportedActionError,
UnexpectedActionError. The ElementsError union is exported for exhaustive
Effect.catchTag checks.
Lifecycle
loadElements opens a single root scope. Calling Elements.dispose() closes
it, which:
- unmounts every element created from this instance,
- detaches the parent-window
messagelistener, - interrupts the per-element listener fibers,
- shuts down the internal PubSub instances.
The Effect API uses the surrounding Scope instead of an explicit
dispose() — closing the scope tears everything down.
Browser support
Modern evergreen browsers (Chrome, Firefox, Safari, Edge). The SDK uses
postMessage, URLSearchParams, and standard DOM APIs — no polyfills
required for supported targets.
Contributing
See CONTRIBUTING.md. Issues and pull requests welcome.
Security
To report a security vulnerability, see SECURITY.md. Please do not open a public issue.
License
MIT — see LICENSE.
