@pulsebyshiga/collect-js
v0.3.0
Published
Tier A embed loader — mounts Pulse Collect payment components into a partner page via a sandboxed iframe and a typed postMessage bridge
Readme
@pulsebyshiga/collect-js
Drop Pulse's embedded payment experience into any web page. collect-js mounts a
Pulse-controlled iframe and gives you a small, fully-typed API around it — you keep the
page, the layout, and the brand; Pulse owns the payment surface. The session token,
deposit addresses, QR codes, and order data never enter your DOM.
Framework-agnostic and dependency-free. The React and React Native SDKs build on it.
Pre-release (0.2.0). The public API may still change.
Requirements
- Any framework, or none — the loader has zero runtime dependencies.
- A modern browser with ES2019 support. Ships an ESM build (
import) and a script-tag IIFE build. - A session token (
cs_*) minted by your backend with@pulsebyshiga/node. Your secret key never reaches the browser.
Installation
npm install @pulsebyshiga/collect-jsNo bundler? Load the IIFE build from a script tag — it exposes a global PulseCollect:
<script src="https://embed.pulse.shiga.io/v1/collect.iife.js"></script>
<script>
const handle = PulseCollect.mount(document.getElementById('pay'), { sessionToken });
</script>Quickstart
Mint a single-order session token on your backend, pass it to the browser, and mount:
import { PulseCollect } from '@pulsebyshiga/collect-js';
const handle = PulseCollect.mount(document.getElementById('pay'), {
sessionToken, // cs_* from your backend — never a secret key
theme: { primaryColor: '#083a9a', fontFamily: 'YourFont, sans-serif' },
onSuccess: (orderId) => showFunded(orderId), // UX signal only — credit off the webhook
onError: (code, message) => report(code, message),
});
// later — on route change, modal close, unmount:
handle.unmount();By default the embed walks the user through Select asset → Choose network → Pay, and
reports every transition through your callbacks. Pass flow: "direct" to skip selection and
render the single payment view for the asset and network chosen at session creation.
How it works
- Your backend creates a collection session and gets a
cs_*token (@pulsebyshiga/node). - You hand that token to the browser and call
PulseCollect.mount(target, { sessionToken }). - The loader renders a sandboxed, Pulse-hosted iframe inside
targetand connects a typedpostMessagebridge. - The user pays inside the iframe; you receive
onReady,onStatusChange,onSuccess, andonErrorevents. - You credit the user from the signed
disbursement.completedwebhook — never from a client-side callback, which can be spoofed.
API
PulseCollect.mount(target, options) → EmbedHandle
Creates the iframe inside target and wires the message bridge. Returns
{ iframe, unmount() }. Throws if sessionToken is missing or is not a cs_* token.
Options
| Option | Type | Default | Description |
| ---------------- | ---------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| sessionToken | string | — (required) | Single-order session token from your backend. |
| component | "payment" \| "quote" \| "status" | "payment" | Full flow, live locked quote only, or progress/status only. |
| flow | "select" \| "direct" | "select" | In-embed asset/network selection, or pinned to the session's values. |
| networks | string[] | all enabled | Narrow the selectable networks for this mount. Intersected with your dashboard configuration — narrowing only, never widening. |
| assets | string[] | all enabled | Narrow the payable assets; same rules as networks. |
| theme | EmbedTheme | design defaults | Visual tokens — see Theming. |
| strings | EmbedStrings | built-in copy | Copy overrides — see Copy. |
| readyTimeoutMs | number | 15000 | Ms to wait for the embed's ready signal before onError("embed_load_timeout"). |
| embedUrl | string | production embed | Embed origin override (local development, partner-subdomain deployment). |
| apiUrl | string | embed default | API base the embed calls with the session token. |
Events
| Callback | Signature | Notes |
| ------------------ | ------------------- | -------------------------------------------------------------------------------------------------- |
| onReady | () | Embed rendered and polling. |
| onStatusChange | (status, orderId) | Every lifecycle transition — see status values below. |
| onSuccess | (orderId) | Order settled. UX signal only — credit users from the signed disbursement.completed webhook. |
| onQuoteExpired | () | Locked quote lapsed; the embed offers recovery itself. |
| onQuoteRefreshed | (lockedUntil) | A new quote window was locked. |
| onError | (code, message) | Load, session, or action failures (e.g. embed_load_timeout, session_load_failed). |
Status values: loading, awaiting_payment, deposit_detected, deposit_confirmed,
converting, completed, underpaid, overpaid, wrong_chain, expired, failed.
Theming
Every token is optional; unset tokens fall back to your dashboard configuration, then to the design defaults.
| Token | Applies to |
| ------------------------------ | ------------------------------------------------------------------------------------ |
| primaryColor | Buttons and accents — your brand color. |
| buttonTextColor | Text on primary buttons. |
| mode | "light" (default) or "dark" palette. |
| fontFamily | Whole component. |
| inkColor / mutedColor | Primary / secondary text. |
| backgroundColor | Panel background. |
| lineColor | Borders, dividers, inactive step markers. |
| successColor / dangerColor | Positive states / failures and final-minute countdown. |
| borderRadius | Control rounding, e.g. "8px". |
| pageBackgroundColor | Full-page (Tier B) backdrop. |
| density | "compact" (default) or "comfortable". |
| attribution | "subtle" (default) or "none" — the "Powered by Pulse" footer is never mandatory. |
Copy
Override any user-facing string via strings: title, subtitle, heroLabel,
successTitle, successSubtitle, expiredTitle, expiredSubtitle, refreshButton,
copyButton, copiedButton. Values may use the placeholders {target} (formatted fiat),
{assetAmount}, {asset}, {network}, and {settlementRef}.
Server-driven configuration
Networks, assets, theme, copy, attribution, and the default flow can also be set once in the
Pulse dashboard (partner_config); the embed applies them with no code change. Precedence is
defaults ← dashboard ← mount options — except the network and asset lists, which only ever
narrow: a mount cannot re-enable what the dashboard disabled, and the server rejects disabled
values at session creation and re-selection.
Security model
- The session token travels in the URL fragment — it is never sent to a server in a request line, and never stored in logs.
- Messages are accepted only from the embed's own origin and window; everything else is ignored.
- The iframe runs with a least-privilege
sandbox(no top navigation, no downloads). - Your
sk_*key and any user PII stay on your backend — this package needs neither.
Advanced
buildEmbedSrc(options)— returns the embed URL and origin without mounting; used by the React Native wrapper, and handy for Tier B (full-page,layout=page) links.formatFiat(amount, currency)/formatAsset(amount)— the embed's own formatters, exported so surrounding partner UI renders amounts identically.
TypeScript
Written in TypeScript and ships its own type definitions — MountOptions, EmbedHandle,
EmbedTheme, EmbedStrings, and the status/event unions are all exported. No @types
package needed.
Related packages
@pulsebyshiga/collect-react— React bindings.@pulsebyshiga/collect-react-native— React Native bindings.@pulsebyshiga/node— backend SDK: sessions, orders, and webhook verification.
License
MIT © Shiga Digital
