Skip to main content
Two hooks bracket a checkout: useCheckoutPreview shows what the customer owes today and what changes before anything is charged, and useCheckoutSession polls a hosted-form session until the PSP confirms. Both are scoped to the portal customer. Every hook returns isLoading: boolean and error: Error | null (see Introduction); the sections below list each hook’s specific returns.

useCheckoutPreview

Returns a server-computed preview of a checkout action — amount due today, the upcoming schedule, credit impact, line items, and warnings — before anything is charged. It calls POST /v1/checkout/sessions/preview with a discriminated input. Pass null to disable (e.g. while inputs are still being assembled). customerId is filled from context — callers omit it.
For plan-change previews, use useSubscription(id).previewChange() instead — it returns a richer shape (proration, entitlement diffs, direction/timing).
Parameters
input
CheckoutPreviewInput | null
required
The action to preview, a discriminated union on purpose. Pass null to hold the query inert.
Returns
  • preview — the CheckoutPreviewResult, or null while loading / when disabled. It extends the shared base shape with the per-purpose details payload:
dueToday
DueToday
Discriminated by kind: charge_now (cents, currency), invoice_later (cents, netDays, estimatedDate), or no_charge.
lineItems
PreviewLineItem[]
{ description, unitAmount, quantity, subtotal, muted? } rows.
subtotal / discount / creditsApplied / tax / total
number
Money breakdown in minor units.
schedule
PreviewSchedule
Optional { date, amountCents? } for the next charge.
creditImpact
CreditImpact
Optional wallet delta — { currencyName, denomination, minorUnitScale, currentBalance, delta, afterBalance, … }.
warnings
PreviewWarning[]
{ code, params? } — e.g. trial_requires_payment_method, customer_has_no_payment_method.
purpose / mode / details
-
Echoes the purpose; details is the per-purpose payload (e.g. details.subscription, details.creditTopup).
  • refresh()() => Promise<void> — refetch the preview (e.g. after the customer edits the amount).
import { useCheckoutPreview } from '@unitpay/react';

export default function TopUpPreview({ creditCurrencyId }: { creditCurrencyId: string }) {
  const { preview, isLoading, error, refresh } = useCheckoutPreview({
    purpose: 'credit_topup',
    creditCurrencyId,
    amountCents: 2000,
  });

  if (isLoading) return <div>Calculating…</div>;
  if (error || !preview) return <div>Couldn’t preview.</div>;

  return (
    <div>
      {preview.dueToday.kind === 'charge_now' && (
        <div>Due today: {preview.dueToday.cents / 100} {preview.dueToday.currency}</div>
      )}
      {preview.creditImpact && (
        <div>New balance: {preview.creditImpact.afterBalance}</div>
      )}
      {preview.warnings.map((w) => <div key={w.code}>{w.code}</div>)}
      <button onClick={() => refresh()}>Recalculate</button>
    </div>
  );
}

useCheckoutSession

Polls GET /v1/checkout/sessions/:id/status and reports when a hosted-form payment resolves. Use it to await the result of a requires_form SettleOutcome — after the Elements form submits, the webhook completer flips the session to completed once the PSP confirms. Polling is tiered: ~1s for the first 30s (hot phase), then ~5s, then it stops and synthesizes expired at maxWaitMs. Parameters
checkoutSessionId
string | null
required
The session to poll. Pass null to leave the hook idle (e.g. before the form step is reached). Changing the id resets the timeout.
options.enabled
boolean
default:"true"
Gate polling without unmounting the hook. Set false to hold the query inert (e.g. before the modal advances to its confirming step).
options.maxWaitMs
number
default:"90000"
Wall-clock cap on the pending window. After this elapses the hook stops polling and surfaces status: 'expired' even if the server row is still pending.
Returns
  • status'idle' | 'pending' | 'completed' | 'expired':
    • 'idle' — no checkoutSessionId, or enabled is false.
    • 'pending' — polling; defaults to this until the first fetch resolves.
    • 'completed' — the PSP confirmed and the session row finalized.
    • 'expired' — terminal server status, or the client-side maxWaitMs timeout fired.
  • data — the CheckoutSessionStatus row, or null before the first response: { id, status, completedAt, subscriptionId, invoiceId }.
import { useCheckoutSession } from '@unitpay/react';
import { useEffect } from 'react';

export default function ConfirmingStep({
  sessionId,
  onDone,
}: {
  sessionId: string;
  onDone: (subscriptionId: string | null) => void;
}) {
  const { status, data } = useCheckoutSession(sessionId, { maxWaitMs: 30_000 });

  useEffect(() => {
    if (status === 'completed') onDone(data?.subscriptionId ?? null);
  }, [status, data, onDone]);

  if (status === 'completed') return <div>All set ✓</div>;
  if (status === 'expired') return <div>Payment successful — provisioning is taking longer than usual.</div>;
  return <div>Confirming your payment…</div>;
}
Pair this with a requires_form outcome: render the <PaymentForm> Elements step, then mount useCheckoutSession(session.id) to await provisioning.

See also

Catalog & pricing

The plans and packages a checkout starts from.

Settlement model

Every SettleOutcome kind and its callback, including requires_form.

Credits & wallets

Top-up and package purchases that feed the preview.