TypeScript — React SDK
@paylera/react ships a <PayleraProvider> plus 12 hooks. The Autumn-compatible read/mutate surface (useFeature, useTrack, useAttach, useCustomer, useBillingPortal, usePricingTable) is primary; Paylera-native lifecycle hooks (useInvoices, useUpgrade, useChangePlan, useCancel) complete the surface. SSR is supported via the /server subpath.
The React SDK never talks to
api.paylera.devdirectly. Every call goes through the merchant-backend relay at/api/paylera/*. The relay authenticates the inbound request, attaches the secret token, and forwards to Paylera.
Install
npm install @paylera/react @paylera/sdk@paylera/sdk is a peer-dep (the React SDK reuses its generated wire-format types). React 18 or 19 is required.
Quick start
import { PayleraProvider } from "@paylera/react";
function App() { return ( <PayleraProvider backendUrl="/api/paylera" authVersion={user?.id}> <YourRoutes /> </PayleraProvider> );}authVersion is an opaque per-identity token (usually user?.id). When it changes — sign-out, account switch — the CSRF token is refetched so the previous session’s token cannot be replayed.
Provider props
| Prop | Type | Default | What it does |
|---|---|---|---|
backendUrl | string | — | Where the merchant relay is mounted (e.g. /api/paylera). |
authVersion | string | number | null | — | Rotates the CSRF on identity change. |
initialState | PayleraInitialState | undefined | Pre-hydrated SSR cache. |
checkStaleTime | number (ms) | 30_000 | Default TanStack Query staleTime for read hooks. |
fetch | FetchLike | global fetch | Inject for tests / SSR. |
tracer | @opentelemetry/api Tracer | — | Opens Paylera.<Op> spans on mutations. |
queryClient | QueryClient | new one | Reuse your existing TanStack client. |
disableCsrf | boolean | false | Skip CSRF when your stack has its own anti-CSRF. |
Granular hooks
import { useFeature, useEntitlements, useCustomer, useTrack, useAttach, useBillingPortal, usePricingTable, useInvoices, useUpgrade, useChangePlan, useCancel,} from "@paylera/react";useFeature(code) — entitlement gate
const { allowed, balance, includedUsage, nextResetAt, isLoading } = useFeature("api_calls");const { track } = useTrack();
if (isLoading) return <Spinner />;if (!allowed) return <Paywall />;
return ( <button onClick={() => track("api_calls", 1, { dedupKey: "btn-1" })}> Use API ({balance} left, resets {nextResetAt}) </button>);useEntitlements()
Returns the full entitlements bundle: { features, privileges, data, isLoading, error, refetch }.
useCustomer()
Resolves the customer record: { customer, isLoading, error, refetch }.
usePricingTable() / usePlans()
The public plan catalog. Identical hooks — the names mirror Autumn’s surface.
const { plans, isLoading } = usePricingTable();useInvoices(opts?)
The customer’s invoices: { invoices, data, isLoading, error, refetch }.
useTrack()
Record a usage event. Accepts an optional dedupKey that becomes the Idempotency-Key header.
const { track, isPending } = useTrack();await track("api_calls", 1, { dedupKey: "btn-click-1" });useAttach()
One-shot subscribe → Paylera-hosted checkout.
const { attach } = useAttach();const res = await attach({ plan_id: "...", success_url: "https://acme.com/success", cancel_url: "https://acme.com/pricing",});window.location.href = res.checkout_url;useBillingPortal()
Mint a customer-portal URL.
const { openBillingPortal } = useBillingPortal();const { url } = await openBillingPortal({});window.location.href = url;useUpgrade() / useChangePlan()
Identical hooks — both wrap POST /api/paylera/subscriptions/{id}/upgrade. Use whichever name reads better.
useCancel()
const { cancel } = useCancel();await cancel({ subscription_id: "...", at_period_end: true });usePaylera() — composite Autumn alias
Returns { attach, check, track } on a single object — handy when migrating from an Autumn-shaped codebase.
Mutation invalidation
| Mutation | Invalidates |
|---|---|
useTrack | useFeature(featureCode), useEntitlements |
useAttach | useCustomer, useEntitlements |
useUpgrade / useChangePlan | useEntitlements, useCustomer, useInvoices |
useCancel | useCustomer, useEntitlements |
Every mutation accepts an optional dedupKey that becomes the Idempotency-Key header. Within TanStack’s mutation-cache window, identical keys are de-duped before they hit the network.
CSRF protocol
<PayleraProvider> fetches GET ${backendUrl}/csrf-token on mount, caches the token in a ref, and attaches Paylera-CSRF-Token: <token> on every state-changing relay call. The relay middleware compares the header to the double-submit cookie with crypto.timingSafeEqual.
Pass disableCsrf when the merchant runs their own anti-CSRF middleware.
Server-side rendering
Use the /server subpath in a Server Component:
// app/pricing/page.tsx — Next.js App Routerimport { PayleraProvider } from "@paylera/react";import { getEntitlements, getCustomer, getFeature, buildInitialState,} from "@paylera/react/server";
export default async function Page() { const initialState = await buildInitialState({ customerId: session.user.payleraCustomerId, apiToken: process.env.PAYLERA_API_TOKEN!, entitlements: true, customer: true, features: ["api_calls"], });
return ( <PayleraProvider backendUrl="/api/paylera" initialState={initialState}> <PricingClient /> </PayleraProvider> );}The SSR helpers require an explicit customerId — they never read ambient request state, which would let one server-component render leak another user’s entitlements through a stale AsyncLocalStorage value.
SSR helpers
| Function | Returns |
|---|---|
getFeature(code, opts) | FeatureCheck — same shape useFeature consumes. |
getEntitlements(opts) | EntitlementsBundle — feeds the useEntitlements cache. |
getCustomer(opts) | Customer |
buildInitialState(opts) | Combined payload for <PayleraProvider initialState={…}>. |
All four accept { customerId, apiToken, baseUrl?, apiVersion? }.
OpenTelemetry
import { trace } from "@opentelemetry/api";
<PayleraProvider backendUrl="/api/paylera" tracer={trace.getTracer("Paylera.React")}> ...</PayleraProvider>Every mutation hook opens a span named Paylera.<Op> (Paylera.Track, Paylera.Attach, …) with the attribute keys defined in the SDK protocol. @opentelemetry/api is an optional peer-dep; without a tracer the spans are no-ops with zero overhead.
API reference
| Hook | Returns |
|---|---|
useFeature(code, options?) | { allowed, balance, usage, includedUsage, unlimited, nextResetAt, data, isLoading, error, refetch } |
useEntitlements() | { features, privileges, data, isLoading, error, refetch } |
useCustomer() | { customer, isLoading, error, refetch } |
usePricingTable() / usePlans() | { plans, data, isLoading, error, refetch } |
useInvoices(opts?) | { invoices, data, isLoading, error, refetch } |
useTrack() | { track(code, value, opts?), isPending, error, reset } |
useAttach() | { attach(input, opts?), isPending, error, data, reset } |
useBillingPortal() | { openBillingPortal(input, opts?), isPending, error, reset } |
useUpgrade() / useChangePlan() | { upgrade | changePlan(input, opts?), isPending, error, reset } |
useCancel() | { cancel(input, opts?), isPending, error, reset } |
usePaylera() | { attach, check, track } — composite Autumn alias |
Cross-package compatibility
@paylera/react@X.Y.Z peer-depends on @paylera/sdk@^X.Y.Z (matching major). Mismatched majors cause an install-time peer-dep warning.
Example
A full Next.js 15 App Router example lives at examples/typescript/nextjs/ in the monorepo — relay route, SSR entitlements, pricing table, gated feature.