DocsSDKs & PluginsReact SDK
React SDK
@saleonix/react lets your React app accept payments with embedded or hosted checkout, Stripe-style. It is browser-only and publishable-key-only by design.
Installation
Terminal
npm install @saleonix/reactRequires React 17 or newer.
How it fits together
Saleonix uses two keys, and only one of them ever belongs in the browser:
| Key | Where | Used for |
|---|---|---|
sk_test_ / sk_live_ | Your server only | Creating and retrieving checkout sessions |
pk_test_ / pk_live_ | Browser (this SDK) | Confirming a session with card details |
The flow is always the same three steps:
- Your server creates a checkout session (
POST /api/v1/checkout/sessionswithsk_). - Your frontend either redirects to the hosted
url, or confirms the session with theclient_secret(embedded checkout — this SDK). - Your server verifies
status === "complete" && payment_status === "paid"before fulfilling. Never trust the redirect alone.
sk_ key. Never put secret keys in frontend code or NEXT_PUBLIC_ / VITE_ env vars.Step 1 — Create a session on your server
app/api/create-checkout/route.ts (Next.js — not part of the SDK)
const res = await fetch('https://saleonix.com/api/v1/checkout/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALEONIX_SECRET_KEY}`, // sk_...
'Content-Type': 'application/json',
'Idempotency-Key': orderRef, // makes retries safe
},
body: JSON.stringify({
price_id: 'clpr1ce...',
quantity: 1,
success_url: 'https://example.com/thanks',
}),
});
const session = await res.json();
// Hosted checkout: send session.url to the client and redirect.
// Embedded checkout: send session.client_secret to the client.Option A — Hosted checkout (simplest)
No provider needed — just send the customer to the session's url:
Hosted redirect
import { loadSaleonix } from '@saleonix/react';
const saleonix = loadSaleonix('pk_test_xxx');
async function handleBuy() {
const { url } = await fetch('/api/create-checkout', { method: 'POST' })
.then((r) => r.json());
saleonix.redirectToCheckout({ url });
}After payment the customer lands on your success_url with ?session_id=cs_... appended. Verify it server-side before fulfilling.
Option B — SecureCheckoutForm (recommended embedded)
SecureCheckoutForm renders the card inputs inside an iframe served from the Saleonix origin. Card data never touches your page's DOM or JavaScript — an XSS bug on your site cannot read it, and your PCI scope stays minimal.
SecureCheckoutForm
import { SaleonixProvider, SecureCheckoutForm } from '@saleonix/react';
function Payment({ clientSecret }: { clientSecret: string }) {
return (
<SaleonixProvider publishableKey="pk_test_xxx">
<SecureCheckoutForm
clientSecret={clientSecret}
collectEmail
submitLabel="Pay €1.18"
onError={(error) => console.warn(error.code, error.message)}
// onSuccess omitted → customer is redirected to your success_url
/>
</SaleonixProvider>
);
}The Pay button and optional email field live in your page (style them via className); only the card fields are inside the iframe. The button enables itself once the frame reports the fields are complete. An optional frameUrl prop overrides the card frame URL from the provider options — rarely needed, since the frame is served from the Saleonix origin by default.
Option C — CheckoutForm (raw card form)
CheckoutForm renders card number / expiry / CVC inputs directly in your page with formatting, validation, inline errors, and a submit button that disables while processing. It works without the hosted card frame, but your page handles raw card data, which increases your PCI DSS scope. Prefer SecureCheckoutForm when you can.
CheckoutForm
import { SaleonixProvider, CheckoutForm } from '@saleonix/react';
function App() {
return (
<SaleonixProvider publishableKey="pk_test_xxx">
<Payment />
</SaleonixProvider>
);
}
function Payment() {
// clientSecret comes from your server (step 1)
return (
<CheckoutForm
clientSecret={clientSecret}
collectEmail
submitLabel="Pay €1.18"
onError={(error) => console.warn(error.code, error.message)}
/>
);
}Props (shared by both forms)
| Field | Type | Required | Description |
|---|---|---|---|
| clientSecret | string | yes | From your server (step 1). |
| sessionId | string | no | Derived from clientSecret when omitted. |
| customerEmail | string | no | Prefill / send with the confirm. |
| collectEmail | boolean | no | Render an email input (default false). |
| submitLabel | string | no | Default "Pay". |
| onSuccess | (session) => void | no | Default: redirect to session.redirect_url. |
| onError | (error) => void | no | Errors are also shown inline. |
| className | string | no | For your own styling. |
| frameUrl | string | no | SecureCheckoutForm only — overrides the card frame URL. |
Option D — useCheckout (custom UI)
useCheckout() reads the client from context, so the component calling it must be a descendant of SaleonixProvider — same setup as Options B and C. Calling it without one (or in the same component that renders the provider) throws useSaleonix must be used inside a <SaleonixProvider>.
Custom form with the hook
import { SaleonixProvider, useCheckout, parseExpiry } from '@saleonix/react';
function App() {
return (
<SaleonixProvider publishableKey="pk_test_xxx">
<CustomPaymentForm clientSecret={clientSecret} />
</SaleonixProvider>
);
}
function CustomPaymentForm({ clientSecret }: { clientSecret: string }) {
const { confirm, processing, error } = useCheckout();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const { session } = await confirm({
clientSecret,
card: { number: '4242424242424242', exp_month: 12, exp_year: 2030, cvc: '123' },
customerEmail: 'buyer@example.com',
autoRedirect: true, // follow session.redirect_url on success
});
}
return (
<form onSubmit={handleSubmit}>
{/* your inputs */}
{error && <p>{error.message}</p>}
<button disabled={processing}>Pay</button>
</form>
);
}Outside React components, use the client directly:
Vanilla client
const saleonix = loadSaleonix('pk_test_xxx');
const { session, error } = await saleonix.confirmCheckoutSession({ clientSecret, card });Error handling
Confirm calls never throw — they resolve to { session } or { error }:
SaleonixError
interface SaleonixError {
type: 'card_error' | 'invalid_request_error' | 'authentication_error'
| 'rate_limit_error' | 'api_error' | 'validation_error' | 'network_error';
code: string; // e.g. 'card_declined'
message: string; // safe to show to the customer
status?: number; // HTTP status (API errors only)
retry_after?: number; // seconds, on 429s
}| Situation | What to do |
|---|---|
card_declined | Terminal — create a NEW session on your server and let the customer try again. |
| Invalid input codes | The session stays payable; the SDK also catches these locally (type: 'validation_error') so they don't consume confirm attempts. |
invalid_response / network_error after submit | The payment may have gone through! Retrieve the session from your server and check payment_status before creating a new one — or you risk double-charging. |
confirm_in_progress | A duplicate confirm was dropped before reaching the network. |
too_many_attempts / rate_limited | Honor retry_after. |
session_expired | Create a new session. |
Test cards
TEST_CARDS
import { TEST_CARDS } from '@saleonix/react';
TEST_CARDS.APPROVED; // 4242 4242 4242 4242
TEST_CARDS.DECLINED; // 4343 4343 4343 4343 → terminal card_declinedFull export reference
| Export | Kind | Description |
|---|---|---|
loadSaleonix(pk, options?) | function | Create a client (synchronous — no script injection needed). |
Saleonix | class | confirmCheckoutSession(), redirectToCheckout(). |
SaleonixProvider | component | Provides the client via context. |
useSaleonix() | hook | Access the client. |
useCheckout() | hook | { confirm, processing, error, session }. |
SecureCheckoutForm | component | Iframe-based payment form (card data stays on the Saleonix origin). |
CheckoutForm | component | Raw-card embedded form. |
sessionIdFromClientSecret(secret) | function | cs_..._secret_... → cs_... |
isSafeRedirectUrl(url) | function | True only for absolute http(s) URLs. |
formatCardNumber, parseExpiry, validateCard, luhnCheck | functions | Card input helpers. |
TEST_CARDS, DEFAULT_API_BASE_URL | constants |
Security guarantees built into the SDK
client_secret, card and customer_email. Redirects are scheme-checked (javascript: and data: URLs are blocked), and live keys require HTTPS.