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 redirects to the hosted
url(see the 3-D Secure note below — confirming with theclient_secretdirectly is currently unavailable). - 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.3-D Secure is required — embedded confirm doesn't work
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();
// Send session.url to the client and redirect (the only path that works today).Option A — Hosted checkout (the only working option today)
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 (currently unavailable — see 3DS note above)
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 (currently unavailable — see 3DS note above)
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 (currently unavailable — see 3DS note above)
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. |
three_d_secure_required | Returned on every environment now — embedded confirm isn't available. Use redirectToCheckout (Option A) so the card is entered and (simulated in TEST, real in LIVE) 3DS-authenticated on Saleonix's hosted page. |
Test cards
TEST_CARDS is kept as a reference for the numbers Saleonix's hosted checkout page accepts in TEST mode — entered there after redirectToCheckout, not passed to confirm.
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.