Skip to main content
Hooks for reading and mutating the subscriptions of the customer in <UnitPayProvider> context — list them, resolve the live one, create a new subscription, change plans, attach addons, and cancel or resume. Every hook returns loading/error state — reads via isLoading: boolean, mutations via isPending: boolean — plus error: Error | null. See Introduction. The sections below list each hook’s specific returns. The four mutation hooks that move money (useCreateSubscription, useChangePlan, useAttachAddon) resolve to a SettleOutcome — the server decides whether to charge inline, mint an inline <PaymentForm>, send an invoice, or take no action — which you handle via the on* settlement callbacks in options. useCancelSubscription and useUncancelSubscription move no money, so they return a plain SubscriptionMutationResult instead.

useSubscriptions

Lists every subscription belonging to the customer — across every product, plan, and status — as one cached list. Takes no arguments; the customer comes from <UnitPayProvider> context. Returnssubscriptions: Subscription[] — every subscription on the customer (up to 50), or [] while loading or when none exist.
import { useSubscriptions } from '@unitpay/react';

export default function SubscriptionList() {
  const { subscriptions, isLoading, error } = useSubscriptions();

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Failed to load subscriptions.</p>;
  if (subscriptions.length === 0) return <p>No subscriptions yet.</p>;

  return (
    <ul>
      {subscriptions.map((sub) => (
        <li key={sub.id}>{sub.planId}{sub.status}</li>
      ))}
    </ul>
  );
}

useActiveSubscription

Answers “what is the customer billing on right now?” It filters useSubscriptions (no extra request) to live statuses — active, trialing, pending, past_due — returning both the first match for single-sub portals and the full filtered list for multi-sub customers. Parameters
planIds
readonly string[]
Restrict to subscriptions whose planId is in this list. Use when the portal is product-scoped — pass the product’s plan ids (e.g. from usePricing(productId)). Omit to consider every plan the customer has.
Returns
  • subscriptionSubscription | null — the first live subscription matching the filter, or null while loading or when none are live.
  • subscriptionsSubscription[] — every live subscription matching the filter. Use this when a customer legitimately has more than one (different products, currencies, addons).
import { useActiveSubscription } from '@unitpay/react';

export default function CurrentPlan() {
  const { subscription, isLoading } = useActiveSubscription();

  if (isLoading) return <p>Loading…</p>;
  if (!subscription) return <p>No active subscription.</p>;

  return (
    <p>
      On {subscription.planId} ({subscription.status}) until{' '}
      {subscription.currentBillingPeriodEnd}
    </p>
  );
}
Scope to one product by passing its plan ids:
import { useActiveSubscription, usePricing } from '@unitpay/react';

export default function ProductPlan({ productId }: { productId: string }) {
  const { pricing } = usePricing(productId);
  const planIds = pricing?.tiers.flatMap((t) => t.plans.map((p) => p.id)) ?? [];
  const { subscription } = useActiveSubscription({ planIds });

  return <p>{subscription ? subscription.planId : 'Not subscribed to this product.'}</p>;
}

useSubscription

A read-only hook that fetches one subscription by id and exposes previewChange, a parameterized read that previews what switching to another plan would cost. Mutations live in the dedicated hooks below. Parameters
subscriptionId
string
The subscription to fetch. The query stays disabled until an id is provided, so it is safe to pass undefined while the id is still loading.
Returns
  • subscriptionSubscription | null — the subscription, or null while loading or when no id is set.
  • previewChange(newPlanId)(newPlanId: string) => Promise<SubscriptionPreviewChange> — previews switching to newPlanId. Returns the full change breakdown: direction (upgrade / downgrade / lateral), timing (immediate / deferred), dueNow, effectiveAt, entitlement diffs, and credit deltas. Memoized, so it is safe in a useEffect dependency array.
import { useEffect, useState } from 'react';
import { useSubscription } from '@unitpay/react';
import type { SubscriptionPreviewChange } from '@unitpay/react';

export default function PlanSwitcher({ subscriptionId }: { subscriptionId: string }) {
  const { subscription, isLoading, previewChange } = useSubscription(subscriptionId);
  const [preview, setPreview] = useState<SubscriptionPreviewChange | null>(null);

  useEffect(() => {
    previewChange('pln_pro_monthly').then(setPreview);
  }, [previewChange]);

  if (isLoading) return <p>Loading…</p>;
  if (!subscription) return <p>Not found.</p>;

  return (
    <div>
      <p>Current plan: {subscription.planId}</p>
      {preview && (
        <p>Switch is a {preview.direction}; due now {preview.dueNow.amount} {preview.dueNow.currency}.</p>
      )}
    </div>
  );
}

useCreateSubscription

Mutation that subscribes the customer in context to a plan. salesMotion defaults to self_serve server-side. useCreateSubscription(options?) takes no id; options is a HandleSettlementOptions object of settlement callbacks (onChargedInline, onRequiresForm, onInvoiceSent, onCreated, onDeferred, onNoAction, …). The returned createSubscription(input) alias accepts:
planId
string
required
The plan to subscribe to.
currency
string
Override the billing currency. Defaults to the plan’s currency.
metadata
Record<string, unknown>
Arbitrary metadata to attach to the subscription.
forceForm
boolean
Force the inline <PaymentForm> even when a payment method is already on file.
idempotencyKey
string
Override the auto-generated Idempotency-Key.
Returns — a React Query mutation plus createSubscription(input) => Promise<SettleOutcome> (an ergonomic alias for mutateAsync; the on* callbacks have already been dispatched against the outcome).
import { useState } from 'react';
import { useCreateSubscription } from '@unitpay/react';
import type { RequiresFormClient } from '@unitpay/react';

export default function Subscribe({ planId }: { planId: string }) {
  const [formClient, setFormClient] = useState<RequiresFormClient | null>(null);

  const { createSubscription, isPending } = useCreateSubscription({
    onChargedInline: () => alert('Subscribed and paid.'),
    onRequiresForm: (r) => setFormClient(r.client),
    onCreated: () => alert('Subscription created (no charge).'),
  });

  return (
    <button onClick={() => createSubscription({ planId })} disabled={isPending}>
      {isPending ? 'Subscribing…' : 'Subscribe'}
    </button>
  );
}

useChangePlan

Mutation that moves an existing subscription to a new plan (upgrade, downgrade, or lateral). The server dispatches inline charge (PLG with a payment method), recovery checkout (no PM or decline), an SLG invoice, or a deferred change. Preview the change first with useSubscription’s previewChange. useChangePlan(subscriptionId, options?) — the subscription id is the first argument; options is a HandleSettlementOptions object. The returned changePlan(input) alias accepts:
newPlanId
string
required
The plan to switch to.
forceForm
boolean
Force the inline <PaymentForm> even when a payment method is already on file.
idempotencyKey
string
Override the auto-generated Idempotency-Key.
Returns — a React Query mutation plus changePlan(input) => Promise<SettleOutcome>.
import { useState } from 'react';
import { useChangePlan } from '@unitpay/react';
import type { RequiresFormClient } from '@unitpay/react';

export default function UpgradeButton({ subscriptionId }: { subscriptionId: string }) {
  const [formClient, setFormClient] = useState<RequiresFormClient | null>(null);

  const { changePlan, isPending } = useChangePlan(subscriptionId, {
    onChargedInline: () => alert('Plan upgraded.'),
    onRequiresForm: (r) => setFormClient(r.client),
    onDeferred: (r) => alert(`Scheduled for ${r.scheduledFor}`),
  });

  return (
    <button onClick={() => changePlan({ newPlanId: 'pln_pro' })} disabled={isPending}>
      {isPending ? 'Upgrading…' : 'Upgrade to Pro'}
    </button>
  );
}

useAttachAddon

Mutation that attaches an addon plan to an existing subscription. Portal callers typically set forceForm so addon purchases always confirm through the inline <PaymentForm> rather than silently debiting the saved card. useAttachAddon(subscriptionId, options?) — the parent subscription id is the first argument; options is a HandleSettlementOptions object (also supports onInvoiceAdded). The returned attachAddon(input) alias accepts:
addonPlanId
string
required
The addon plan to attach.
forceForm
boolean
Force the dispatcher to mint requires_form even when a payment method is on file, so the purchase always shows the inline <PaymentForm> and is explicitly confirmed — no silent off-session debit of the default card.
idempotencyKey
string
Override the auto-generated Idempotency-Key.
Returns — a React Query mutation plus attachAddon(input) => Promise<SettleOutcome>.
import { useState } from 'react';
import { useAttachAddon } from '@unitpay/react';
import type { RequiresFormClient } from '@unitpay/react';

export default function AddSeats({ subscriptionId }: { subscriptionId: string }) {
  const [formClient, setFormClient] = useState<RequiresFormClient | null>(null);

  const { attachAddon, isPending } = useAttachAddon(subscriptionId, {
    onChargedInline: () => alert('Addon attached.'),
    onRequiresForm: (r) => setFormClient(r.client),
  });

  return (
    <button
      onClick={() => attachAddon({ addonPlanId: 'pln_extra_seats', forceForm: true })}
      disabled={isPending}
    >
      {isPending ? 'Adding…' : 'Add seats'}
    </button>
  );
}

useCancelSubscription

Mutation that cancels a subscription. Cancellation moves no money, so — unlike the mutations above — it does not return a SettleOutcome; it resolves to a plain SubscriptionMutationResult. useCancelSubscription(subscriptionId, options?) — the subscription id is the first argument. options carries a single onSuccess(result: SubscriptionMutationResult) => void callback. The returned cancel(input?) alias accepts (all optional — call cancel() for a default period-end cancel):
cancelImmediately
boolean
Cancel right now instead of at the end of the current billing period.
cancellationReason
string
Machine-readable reason code stored on the subscription.
cancellationNote
string
Free-text note explaining the cancellation.
Returns — a React Query mutation plus cancel(input?) => Promise<SubscriptionMutationResult>, resolving to { subscriptionId, status, cancelAtPeriodEnd, effectiveAt }.
import { useCancelSubscription } from '@unitpay/react';

export default function CancelButton({ subscriptionId }: { subscriptionId: string }) {
  const { cancel, isPending } = useCancelSubscription(subscriptionId, {
    onSuccess: (result) =>
      alert(
        result.cancelAtPeriodEnd
          ? `Cancels on ${result.effectiveAt}`
          : 'Canceled immediately.',
      ),
  });

  return (
    <button onClick={() => cancel({ cancellationReason: 'too_expensive' })} disabled={isPending}>
      {isPending ? 'Canceling…' : 'Cancel subscription'}
    </button>
  );
}

useUncancelSubscription

Mutation that reverses a pending cancellation, keeping the subscription live. Like cancel, it moves no money and resolves to a plain SubscriptionMutationResult. useUncancelSubscription(subscriptionId, options?) — the subscription id is the first argument. The uncancel alias takes no input (call uncancel()). options carries a single onSuccess(result: SubscriptionMutationResult) => void callback. Returns — a React Query mutation plus uncancel() => Promise<SubscriptionMutationResult>, resolving to { subscriptionId, status, cancelAtPeriodEnd, effectiveAt }.
import { useUncancelSubscription } from '@unitpay/react';

export default function ResumeButton({ subscriptionId }: { subscriptionId: string }) {
  const { uncancel, isPending } = useUncancelSubscription(subscriptionId, {
    onSuccess: (result) => alert(`Subscription is ${result.status} again.`),
  });

  return (
    <button onClick={() => uncancel()} disabled={isPending}>
      {isPending ? 'Resuming…' : 'Keep my subscription'}
    </button>
  );
}

See also

Settlement model

The SettleOutcome union and on* handlers the mutation hooks dispatch.

Invoices

Preview renewals with useUpcomingInvoice and collect payment.

Entitlements & gates

What each subscription grants the customer.

Customer

The customer these subscriptions belong to.