@pulsebyshiga/collect-react
v0.5.1
Published
React SDK for Pulse Collect — <PulseCollectPayment/> (hosted iframe) and <PulseCheckout/> (in-app, apiKey-direct), plus useRate.
Readme
@pulsebyshiga/collect-react
The React SDK for Pulse Collect. Two ways to render the payment moment — pick per component — plus a rate-preview hook. Your brand and your screen; Pulse handles the payment surface and settlement.
Pre-release (0.2.0). The public API may still change.
Which component?
| Component | Renders where | Credential | Use when |
| ----------------------- | -------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| <PulseCheckout> | your DOM as a centered modal (our UI, no iframe) | apiKey (client → Pulse) | you control the runtime (server components / native / a trusted app) and want our UI in your own DOM (not a Pulse iframe) |
| <PulseCollectPayment> | Pulse-controlled iframe | session token (cs_*) | untrusted browser — the key stays on your server; Pulse owns the payment surface |
Both render the identical payment UI (quote + countdown, send/QR or bank-transfer, live status). They differ only in where the UI runs and which credential drives it.
Requirements
- React ≥ 18 (peer dependency), ESM.
<PulseCheckout>: a Pulse API key, plusimport '@pulsebyshiga/collect-react/styles.css'once in your app.<PulseCollectPayment>: a session token (cs_*) minted by your backend with@pulsebyshiga/node.
Installation
npm install @pulsebyshiga/collect-react<PulseCheckout> — in-app, apiKey
Renders our payment UI in your app as a centered modal and talks to Pulse itself: it quotes,
creates the order from request, shows the pay instructions, and polls status until settlement.
The modal is open whenever request is set and closed when it is null/omitted — you own
that state. Pass onClose to get a × button and Esc-to-close; omit it for a fully
parent-controlled modal. (Backdrop clicks never close it.) The modal is portaled to
document.body and is SSR-safe (it renders nothing on the server).
Need the panel inline instead of a modal? Use the exported <CheckoutFlow> directly (see
Advanced — headless client).
import { PulseCheckout, useApiKey } from '@pulsebyshiga/collect-react';
import '@pulsebyshiga/collect-react/styles.css'; // once, anywhere in your app
export function Checkout() {
return (
<PulseCheckout
auth={useApiKey(process.env.PULSE_API_KEY!)}
request={{
direction: 'offramp', // crypto in → fiat out
amount: '100.00',
sourceCurrency: 'USDC',
destinationCurrency: 'NGN',
network: 'BASE', // engine networks are UPPERCASE
customer: { id: 'user_123', name: 'Ada Lovelace', email: '[email protected]' },
account: {
accountNumber: '0123456789',
accountName: 'Ada Lovelace',
bankCode: '044',
bankName: 'Access Bank',
},
}}
onStatusChange={(status, orderId) => track(status, orderId)}
onSuccess={(orderId) => router.push(`/done/${orderId}`)} // UX only — credit off the webhook
onError={(code, message) => toast.error(message)}
/>
);
}For an onramp (fiat in → crypto out), pass direction: 'onramp' with destinationAddress
instead of account; the component renders the virtual bank account to pay into.
Security. An API key in a real browser is readable from devtools —
<PulseCheckout>warns (it doesn't block), so the choice is yours. Hold the key only in a trusted runtime (server components, React Native, a BFF). For an untrusted browser, use<PulseCollectPayment>with a session token instead.
<PulseCollectPayment> — hosted iframe, session token
Mounts a Pulse-controlled iframe into your page. The key, the user's BVN/NIN, and order internals never enter your DOM.
import { PulseCollectPayment } from '@pulsebyshiga/collect-react';
<PulseCollectPayment
sessionToken={sessionToken} // from your backend: pulse.collectionSessions.create(...)
theme={{ primaryColor: '#083a9a' }}
strings={{ title: 'Add {target} to your safe' }}
onSuccess={(orderId) => showFunded(orderId)} // UX only — credit off the webhook
onError={(code, message) => report(code, message)}
className="pay-slot"
/>;Every MountOptions field is
accepted as a prop (sessionToken, component, flow, networks, assets, theme,
strings, readyTimeoutMs, embedUrl, apiUrl, all callbacks) plus className / style.
The iframe remounts only when sessionToken/embedUrl/apiUrl change; callbacks are read
from a ref, so inline closures never tear the surface down. SSR-safe. Theming, copy
placeholders, events, and the security model are documented in
@pulsebyshiga/collect-js.
useRate / <PulseRate> — rate preview
Show the live rate before any transaction. The browser calls your backend proxy (which
uses pulse.rates.preview — see @pulsebyshiga/node),
so no key sits on the client.
import { PulseRate, useRate } from '@pulsebyshiga/collect-react';
// Drop-in line:
<PulseRate from="USDC" to="NGN" amount={amount} />;
// Or headless, with your own transport + auto-refresh:
const { data, loading, error, refresh } = useRate(
{ from: 'USDC', to: 'NGN', amount },
{ endpoint: '/api/pulse/rate', refreshMs: 30_000 },
);
// data → { rate, youSend, youReceive, expiresAt, quoteId }Auth helpers
import { useApiKey, useSession } from '@pulsebyshiga/collect-react';
useApiKey('pk_live_…'); // trusted runtimes — full engine access
useSession('cs_…'); // browser-safe, single-order session tokenTrigger layer — hook & button
Prefer a trigger over managing state yourself? Two optional wrappers own the modal's open-state for
you (both render the same <PulseCheckout> under the hood).
usePulseCheckout(options) — returns { open, close, isOpen, node }. Call open(request) to
launch; render node once.
import { usePulseCheckout, useApiKey } from '@pulsebyshiga/collect-react';
function Pay() {
const { open, node } = usePulseCheckout({ auth: useApiKey(process.env.PULSE_API_KEY!) });
return (
<>
<button onClick={() => open(buildOrder())}>Pay</button>
{node}
</>
);
}<PulseCheckoutButton> — a plain button that opens the modal on click. Use it when the order
is known at render; use the hook when you build it on click.
import { PulseCheckoutButton, useApiKey } from '@pulsebyshiga/collect-react';
<PulseCheckoutButton auth={useApiKey(process.env.PULSE_API_KEY!)} request={order} className="btn">
Withdraw to bank
</PulseCheckoutButton>;Advanced — headless client
For a fully custom UI, the same primitives <PulseCheckout> uses are exported:
createPulseClient(auth) (quote → create order → getOfframpOrder/getOnrampOrder for
polling) and normalizeOrder(created) (engine order → the display shape). You provide the
rendering.
Related packages
@pulsebyshiga/collect-js— the framework-agnostic loader these bindings wrap.@pulsebyshiga/collect-react-native— React Native bindings.@pulsebyshiga/node— backend SDK: sessions, orders, rate preview, webhook verification.
License
MIT © Shiga Digital
