@fluxisus/react
v0.2.0
Published
Official React SDK for Fluxis payment UI — QR codes, compatible apps, and deep links
Readme
@fluxisus/react
Official React SDK for Fluxis payment UI — hosted checkout widget, QR codes, countdown timer, address copy, payment status badge, and compatible wallet deep links.
Install
npm install @fluxisus/react qrcode.react react react-domQuick start
Drop-in checkout widget
Your backend creates a payment request and returns a CheckoutSession. Pass it to CheckoutWidget — it handles the full payment UI automatically:
import { FluxisProvider, CheckoutWidget } from '@fluxisus/react';
import type { CheckoutSession } from '@fluxisus/react';
function CheckoutPage({ session }: { session: CheckoutSession }) {
return (
<FluxisProvider>
<CheckoutWidget session={session} style={{ maxWidth: 480 }} />
</FluxisProvider>
);
}Poll your backend for status updates and pass the refreshed session to keep the widget in sync.
Building a custom UI
Use the lower-level components directly with a NASPIP token:
import { FluxisProvider, FluxisQrCode, CompatibleApps } from '@fluxisus/react';
function Checkout({ token }: { token: string }) {
return (
<FluxisProvider theme={{ colorPrimary: '#2563eb' }}>
<FluxisQrCode token={token} size={280} />
<CompatibleApps token={token} />
</FluxisProvider>
);
}Components
CheckoutWidget
All-in-one hosted checkout UI. Renders the correct screen based on session.status:
| Status | UI |
|--------|----|
| pending | Amount · countdown · QR (desktop) or wallet buttons (mobile) · copy address |
| confirming | Animated spinner + confirmation message |
| completed | Success icon + return link |
| expired | Clock icon + restart button |
<CheckoutWidget session={session} style={{ width: '100%', maxWidth: 480 }} />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| session | CheckoutSession | required | Payment session from your backend |
| className | string | — | Wrapper class |
| style | CSSProperties | — | Wrapper style |
CountdownTimer
Counts down in real time to a payment expiration timestamp. Changes color to amber at ≤ 2 min and red at ≤ 1 min. Calls onExpire exactly once at zero.
<CountdownTimer expiresAt={session.expires_at} onExpire={() => setExpired(true)} />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| expiresAt | string | required | ISO 8601 expiration timestamp |
| onExpire | () => void | — | Called once when timer reaches zero |
| className | string | — | Wrapper class |
AddressCopyButton
Renders a truncated crypto address with a copy-to-clipboard button. Shows a checkmark confirmation for 2 seconds after copying.
<AddressCopyButton address="0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| address | string | required | Wallet address to copy |
| className | string | — | Wrapper class |
PaymentStatusBadge
Inline colored badge mapping a payment status to a readable label.
<PaymentStatusBadge status={session.status} />| Status | Label | Color |
|--------|-------|-------|
| pending | Pending | Blue |
| confirming | Confirming | Amber |
| completed | Completed | Green |
| expired | Expired | Gray |
AmountDisplay
Formats and renders a payment amount with its currency code.
<AmountDisplay amount={session.amount} currency={session.currency} />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| amount | string \| number | required | Payment amount |
| currency | string | required | Currency code (e.g. USD) |
| className | string | — | Wrapper class |
FluxisQrCode
Renders a QR code encoding the NASPIP token with the Fluxis logo in the center.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| token | string | required | NASPIP token (naspip;…) |
| size | number | 280 | QR size in pixels |
| level | 'L' \| 'M' \| 'Q' \| 'H' | 'H' | Error correction (H recommended with logo) |
| logo | string | bundled Fluxis logo | Center image URL or data URI (overlaid on QR) |
| logoSize | number | 21.4% of size | Logo dimensions |
| fgColor | string | theme | Foreground color |
| bgColor | string | theme | Background color |
| className | string | — | Wrapper class |
| style | CSSProperties | — | Wrapper style |
CompatibleApps
On mobile, shows "Pay with <app>" buttons that open wallet deep links. On desktop, shows the QR code and an informational list of compatible apps.
Fetches app metadata from https://assets.fluxis.us/sdk-assets/compatible-apps.json by default.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| token | string | required | NASPIP token for deep links |
| apps | CompatibleApp[] | fetch | Override the apps list |
| include | string[] | — | Filter by app name |
| exclude | string[] | — | Exclude by app name |
| forcePlatform | 'mobile' \| 'desktop' | auto-detect | Override platform behavior |
| renderApp | (app, button) => ReactNode | — | Custom render per app |
CompatibleAppsMarquee
Horizontal showcase that scrolls compatible app logos continuously from right to left. Pauses on hover.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| width | number \| string | '100%' | Viewport width |
| height | number | 56 | Viewport height in pixels |
| speed | number | 24 | Seconds per full scroll loop |
| gap | number | 12 | Gap between app cards (px) |
| showAppName | boolean | true | Show app name next to logo |
| include / exclude | string[] | — | Filter by app name |
| onAppClick | (app) => void | — | Optional click handler per app |
<CompatibleAppsMarquee width={480} height={56} speed={18} />PayWithAppButton
Single "Pay with <display_name>" button with app icon and deep link.
FluxisProvider
Optional wrapper that injects CSS variables (--fluxis-*) for global theming.
<FluxisProvider theme={{ colorPrimary: '#7c3aed', buttonBg: '#1e1b4b' }}>
{children}
</FluxisProvider>Theming
Override priority: component prop > CSS variable from provider > default theme.
Available CSS variables:
--fluxis-color-bg,--fluxis-color-fg,--fluxis-color-primary--fluxis-color-border,--fluxis-color-muted--fluxis-radius,--fluxis-font-family--fluxis-qr-fg,--fluxis-qr-bg--fluxis-button-bg,--fluxis-button-fg,--fluxis-button-hover-bg
Hooks
usePaymentStatus
Polls your own backend route for live payment status — it never calls the Fluxis API directly and accepts no credentials. Your backend is expected to proxy pointOfSale.getPaymentRequest() from @fluxisus/sdk.
import { usePaymentStatus } from '@fluxisus/react';
function CheckoutStatus({ paymentRequestId }: { paymentRequestId: string }) {
const { status, data, error, isPolling, refetch } = usePaymentStatus(
`/api/payment-status/${paymentRequestId}`,
);
if (status === 'completed') return <p>Payment received!</p>;
return (
<div>
<p>Status: {status ?? 'loading…'}</p>
<button onClick={refetch}>I already paid — check now</button>
</div>
);
}Your route (e.g. GET /api/payment-status/:id) should call fluxis.pointOfSale.getPaymentRequest(posId, id) server-side and return the result as JSON — usePaymentStatus reads the status field from whatever your route returns.
| Option | Type | Default | Description |
|---|---|---|---|
| statusUrl | string | required | Your backend route to poll |
| pollInterval | number | 5000 | Poll cadence in ms |
| enabled | boolean | true | Pause/resume polling without unmounting |
Returns { status, data, error, isPolling, refetch }. Polling stops automatically once status reaches a terminal state (completed, expired, failed, overpaid, underpaid). On fetch errors, polling backs off exponentially (capped) and resets once a request succeeds again. Call refetch() for an on-demand check (e.g. an "I already paid" button) — it fetches immediately and resets the poll timer so it doesn't double-fire right after.
Clock-skew offset. On every successful response, the hook reads the Date response header and stores serverDate - Date.now() in FluxisProvider's shared context, for use by other components doing skew-corrected countdowns. This degrades silently: if your route is same-origin, no extra config is needed; if it's cross-origin, the browser hides the Date header by default unless you add:
Access-Control-Expose-Headers: DateWithout that header, the offset simply stays 0 — nothing breaks, it's advisory only.
Utilities
import { isValidNaspipToken, buildDeepLink } from '@fluxisus/react';
isValidNaspipToken('naspip;fluxis.us;abc'); // true
buildDeepLink('https://wallet.app/pay?token=[NASPIP_TOKEN]', token);Security
- Never expose API keys or secrets in frontend code.
- NASPIP tokens are safe to display client-side when created by your server.
- Webhook verification is server-only.
License
MIT
