Skip to main content

Table of Contents

Installation

Install the required packages:

Peer Dependencies

SolvaPay React requires:
  • react ^18.2.0 || ^19.0.0
  • react-dom ^18.2.0 || ^19.0.0
  • @stripe/react-stripe-js (for payment forms)
  • @stripe/stripe-js (for Stripe integration)

Basic Setup

1. Wrap Your App with Provider

The SolvaPayProvider is required to use SolvaPay hooks and components:

2. Zero-Config Usage

By default, SolvaPayProvider uses these API endpoints:
  • /api/check-purchase - Check purchase status
  • /api/create-payment-intent - Create payment intents
  • /api/process-payment - Process payments
If your backend uses these routes, no configuration is needed!

Provider Configuration

Custom API Routes

If your backend uses different API routes, configure them:

With Supabase Authentication

Use the Supabase auth adapter for automatic user ID extraction:

Custom Authentication Adapter

Create a custom auth adapter for other authentication systems:

Components

PaymentForm

A complete payment form component with Stripe integration:

PaymentForm Props

  • planRef (required) - Plan reference to subscribe to
  • productRef (optional) - Product reference for usage tracking
  • onSuccess - Callback when payment succeeds
  • onError - Callback when payment fails
  • returnUrl - Optional return URL after payment
  • submitButtonText - Custom submit button text (default: “Pay Now”)
  • className - Custom CSS class for form container
  • buttonClassName - Custom CSS class for submit button

PricingSelector

Select a pricing option from available options using the render-prop API:

PurchaseGate

Conditionally render content based on purchase status using the compound primitive. Match by productRef (any plan of that product) or planRef (specific plan). Both together require they match on the same active purchase.

ProductBadge

Display product subscription information using the render-prop pattern:

TopupForm

A compound component for one-off credit top-ups. It wraps useTopup and Stripe Elements, so you compose the parts you need instead of accepting a fixed layout:

Compound parts

  • TopupForm.Root — provides context and drives the top-up. Accepts the props below.
  • TopupForm.PaymentElement — renders the Stripe Payment Element.
  • TopupForm.SubmitButton — confirms the payment; disabled until the card input is complete.
  • TopupForm.Loading — shown while the payment intent and Stripe are initializing.
  • TopupForm.Error — renders the current error, if any.
  • TopupForm.AmountPicker — optional in-place amount picker (re-exported from AmountPicker).
  • TopupForm.LegalFooter — mandate / legal copy footer.

Root props

  • amount (required) — top-up amount in minor units (e.g. 1000 = $10.00).
  • currency — ISO 4217 currency code (default: USD).
  • autoRecharge — optional AutoRechargeInput to enable auto-recharge in the same payment (see Credit Top-Ups & Auto-Recharge).
  • onSuccess(paymentIntent, extras?) — fires once the customer is fully credited. extras.creditsAdded carries the wallet delta when the backend reports it.
  • onError(error) — called when the payment fails.
  • returnUrl — return URL used for redirect-based payment methods (defaults to the current URL).
  • className / buttonClassName — styling hooks.
Credits are booked by SolvaPay’s webhook handler. When your backend implements the /api/process-topup-payment route, TopupForm waits for that round-trip so onSuccess only fires once the credit has actually landed. Transports without that route fall back to firing on Stripe confirmation.

AutoRecharge

A drop-in component that lets a customer turn on automatic credit top-ups. It renders a summary card with a trigger that opens a modal for configuring a balance threshold, a fixed top-up amount, and an optional monthly spend cap (under Advanced). When enabled, SolvaPay charges the saved card off-session and mints credits whenever the balance falls below the threshold — no manual checkout.

AutoRecharge Props

  • currency — ISO 4217 currency code (default: USD).
  • defaultThresholdAmountMajor — pre-fills the threshold field (form default: 5).
  • defaultTopupAmountMajor — pre-fills the top-up field (form default: 10).
  • deferCardSetup — when true, saving stages the config without creating a Stripe SetupIntent and fires onPendingConfig instead. Use this to arm auto-recharge inside a top-up payment (see below).
  • onPendingConfig(payload) — receives the AutoRechargeInput to forward into TopupForm when deferCardSetup is set.
  • onSetupRequired(result) — called when a separate card-setup step is required (non-deferred flow).
  • onSaved(result) / onDisabled() — lifecycle callbacks.
  • className — styling hook.
The form is opt-in (enabled: false by default). Amounts can be entered in either display currency or credits; the component handles the Stripe SetupIntent and the 3DS redirect return internally. Set an optional maxMonthlySpendMajor cap (blank = unlimited) in the form. The summary card shows current-period spend when a cap is configured, and a Monthly spend limit reached status when the next recharge would exceed the cap. If a series of off-session charges keeps declining, the card shows a text-only “payment failed” status — see the failed-recharge guidance for building a card-update prompt. For full layout control, compose the primitive from @solvapay/react/primitives: AutoRecharge.MaxMonthlySpendField, AutoRecharge.MonthlySpend, and AutoRecharge.Status (cap-reached and failed badges).

Hooks

usePurchase

Check purchase status and access purchase data:

usePurchase Return Values

  • loading - Boolean indicating the first purchase check for this user is in progress. Stays false on background refetches — gate your initial skeleton on this.
  • isRefetching - Boolean indicating a background refetch is in progress (first fetch already completed). Use for subtle “refreshing” indicators that shouldn’t remount the UI.
  • purchases - Array of purchase objects
  • activePurchase - The active purchase (or null)
  • hasPaidPurchase - Boolean indicating if user has paid purchase
  • activePaidPurchase - The active paid purchase (or null)
  • hasPurchase(criteria?) - Predicate with AND semantics. Pass { productRef }, { planRef }, or both (both must match the same active purchase). Call with no arguments to check for any active purchase. Mirrors <PurchaseGate.Root> prop shape.
  • refetch - Function to manually refetch purchase status

useCheckout

Programmatic checkout flow:

useCustomer

Access customer information:

usePlans

Fetch available plans:

useSolvaPay

Access all SolvaPay functionality:

useTopup

Manage a credit top-up flow programmatically. Handles payment-intent creation and Stripe initialization — the top-up analogue of useCheckout:

useTopup Options

  • amount (required) — top-up amount in minor units (e.g. 1000 = $10.00).
  • currency — ISO 4217 currency code (default: USD).
  • autoRecharge — optional AutoRechargeInput to enable auto-recharge in the same payment.

useTopup Return Values

  • loadingtrue while the payment intent is being created.
  • error — the last error, or null.
  • stripePromise — the resolved Stripe instance for mounting Elements.
  • clientSecret — the payment-intent client secret.
  • startTopup() — creates the payment intent and initializes Stripe.
  • reset() — clears state to start over.

useAutoRecharge

Read and manage the customer’s auto-recharge configuration. Backed by a short-lived module cache so multiple components share one request:

useAutoRecharge Return Values

  • config — the current AutoRechargeConfig, or null if none exists.
  • loading / saving / disabling — in-flight state flags.
  • error — the last error, or null.
  • refresh(force?) — re-fetch the config.
  • save(input) — set or update the config. Accepts SaveAutoRechargeInput (see the top-up section for the maxMonthlySpendMajor cap and deferSetupIntent).
  • disable() — turn auto-recharge off.
config.status is active, disabled, pending_setup, or failed. Read config.failureCount alongside status === 'failed' to decide when to prompt the customer to update their card.

Payment Flow

Simple Payment Flow

Use PaymentForm for a complete payment flow:

Custom Payment Flow

Build a custom payment flow with hooks:

Payment Confirmation

Payment success is gated on real confirmation from Stripe — a form only reports success once the payment intent actually succeeded (and, where the backend route is wired, once SolvaPay has processed it). Two pieces make this work: the confirmPayment utility and the built-in return-path resume.

Payment Element is the default

PaymentForm and TopupForm render the Stripe Payment Element by default, which supports cards plus redirect- and async-based payment methods (SEPA, iDEAL, and others).
The Card Element surface is deprecated: PaymentForm.CardElement, ConfirmPaymentMode: 'card-element', and StripePaymentFormWrapper remain as backwards-compatible shims (since @solvapay/react 1.5.0) and will be removed in the next major version. Migrate to the default Payment Element rendering.

confirmPayment

If you build your own submit handler, use confirmPayment instead of calling Stripe directly. It wraps Elements submission and confirmation, and returns a discriminated result:
pending maps to Stripe’s processing state. The built-in forms surface it as a message and keep the customer on the form — onSuccess never fires from a pending payment.

Return-path resume

Redirect-based payment methods send the customer away and back. PaymentForm and TopupForm handle the return automatically: on mount they read the payment_intent_client_secret query parameters Stripe appends to the returnUrl, retrieve the intent, strip the parameters from the URL, and resume — running handleNextAction if 3DS is still required, showing the pending message while processing, and firing onSuccess once the intent succeeded and the payment was processed. You only need to make sure returnUrl points at the page that renders the form.

Multi-Currency Plans & Top-Ups

Plans can carry per-currency pricing through a pricingOptions array (currency, price, optional basePrice / setupFee, one entry marked default). Single-currency plans without the array behave as before.

Plan checkout

PlanSelector shows a currency switcher when the product’s plans span more than one currency, and each plan card can render its per-currency price. The selected currency flows through checkout automatically — useCheckout passes it when creating the payment. For custom UIs, resolve prices with the helpers:
If a customer selects a currency the plan has no pricingOptions entry for, checkout rejects with an explicit “currency not supported” error listing the available currencies.

Top-up currencies

Credit top-ups use a separate currency list: the provider’s defaultCurrency plus any additional supportedTopupCurrencies configured in the SolvaPay Console. Plan pricing currencies are never used for top-ups. useCheckoutFlow exposes topupCurrencies, topupCurrency, and setTopupCurrency for building a currency picker on the amount step; pass the chosen currency to TopupForm.Root / useTopup.

Credit Top-Ups & Auto-Recharge

Credit-based products let customers buy a balance and spend it as they use your API. SolvaPay supports both one-off top-ups and auto-recharge, where the balance is refilled automatically once it drops below a threshold. All three flows below are powered by @solvapay/next route helpers.

Backend routes

Add these API routes once — the React components and hooks call them through the provider. All are one-liners over @solvapay/next:
The /api/process-topup-payment route is what lets TopupForm confirm that the credit actually landed before firing onSuccess. The three auto-recharge verbs share a single route and a single provider key, api.autoRecharge (default /api/auto-recharge).

Standalone top-up

Render TopupForm with the amount in minor units. Credits are booked by the webhook handler, and — because the /api/process-topup-payment route is wired — onSuccess fires only once the credit has landed:

Enable auto-recharge in the same payment

You can arm auto-recharge as part of the initial top-up charge — there’s no separate card-setup step. Render <AutoRecharge deferCardSetup> on the amount step, capture the pending config, and pass it into TopupForm.Root:
With deferCardSetup, saving the AutoRecharge form sends deferSetupIntent: true (no inline SetupIntent) and fires onPendingConfig. The backend creates the payment intent with setup_future_usage: 'off_session', saves the card on that same charge, and activates the config (pending_setupactive) when the webhook lands — so the customer configures and funds auto-recharge in one step.

Managing auto-recharge on its own

Outside checkout, drop <AutoRecharge> into an account or settings page. It reads and writes through the /api/auto-recharge route:

Reacting to auto-recharge on usage

When you call trackUsage (via /api/track-usage), the response’s creditDebit.autoRecharge.triggered flag signals that an off-session charge was initiated — it does not mean the credits are in that same response. Call balance.reconcileAfterUsageDebit({ expectIncrease: true }) only when triggered is true; it polls the balance (~32s grace) and counts back-to-back recharges so the badge converges once the credit lands:
Configuration, limits, and pricing. Auto-recharge uses a balance threshold plus a fixed top-up amount. When enabled, both must be greater than zero, the top-up must be at least the threshold, and both must be at most 10,000 major units. The top-up amount must also clear Stripe’s per-currency minimum (for example $0.50 for USD/EUR/CHF/CAD/AUD, £0.30 for GBP, 30 kr for SEK/NOK, 2.50 kr for DKK), and the currency must be one of the provider’s supported top-up currencies.An optional maxMonthlySpendMajor cap limits how much auto-recharge spend is allowed per UTC calendar month. Set it in the <AutoRecharge> form under Advanced (or pass it through useAutoRecharge().save({ ..., maxMonthlySpendMajor }) and the /api/auto-recharge route). When the cap is hit mid-month, the config stays active and charges resume automatically next UTC month. The drop-in component shows a spend line ($45 / $100 this month) and a Monthly spend limit reached status when applicable; read config.monthlySpendMinor and config.monthlySpendPeriod if you build a custom UI.Thresholds and amounts are stored in display-currency minor units and re-resolved to credits against live FX at trigger time (no fixed credit threshold is stored). When a config has a display block, render its display.formatted values verbatim rather than re-deriving them from minor units.
Off-session declines are surfaced explicitly. If auto-recharge charges keep failing, useAutoRecharge().config.status flips to failed after repeated declines (and failureCount increments). Declines are not returned in the trackUsage / usage-debit response — they arrive via the customer.credit.auto_topup_failed webhook (see Webhooks). The drop-in <AutoRecharge> renders a text-only “payment failed” status; to prompt a fix, build your own banner off config.status === 'failed' and pair it with <UpdatePaymentMethodButton> so the customer can update their card and resume.
Migrating early-adopter route keys. The separate api.getAutoRecharge, api.saveAutoRecharge, and api.disableAutoRecharge provider keys were collapsed into a single api.autoRecharge key (default /api/auto-recharge). If you set any of the old keys, rename them to api.autoRecharge.

Purchase Management

Check Purchase Status

Display Purchase Details

Refresh Purchase Status

Complete Example

Here’s a complete React application with SolvaPay integration:

Styling

SolvaPay components are headless and don’t include default styles. Style them to match your design system:

Best Practices

  1. Provider Placement: Place SolvaPayProvider at the root of your app, above all routes.
  2. Error Handling: Always handle errors from hooks and components.
  3. Loading States: Show loading states while purchase checks are in progress.
  4. Refetch After Payment: Call refetch() after successful payment to update purchase status.
  5. Type Safety: Use TypeScript for better type safety and autocomplete.
  6. Custom Styling: Style components to match your design system.

Next Steps