caliper uidocs

Payment recovery

Pro

When a payment fails: a banner that says what went wrong and when it will be retried, and a recovery page with every unpaid invoice, the retry schedule, the date the workspace becomes read-only and a retry that reports the bank’s answer.

Installs components/ui/payment-recovery.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add @caliper/payment-recovery

We couldn't collect €144.00. The bank said the card has insufficient funds. We'll try again on 13 Oct (attempt 2 of 4).

Last try to collect €144.00. The card was declined three times. If it fails, the workspace becomes read-only on 24 Oct.

€180.00 is overdue

2 invoices could not be paid with Visa ending 4242.

  • INV-2026-0912Failed 1 Oct · Insufficient funds€144.00
  • INV-2026-0877Failed 18 Sept · Card expired€36.00
  1. 1 Oct · Declined
  2. 4 Oct · Declined
  3. 13 Oct · Automatic retry
  4. 20 Oct · Automatic retry
  5. 24 Oct · Workspace becomes read-only. People can sign in and export, but not edit.
  6. 22 Jan · Data is deleted unless the balance is paid.

Questions? Talk to billing

The first retry is declined; the second goes through.

When to use #

Use it for

  • Subscription products that retry failed charges.
  • The page a dunning email links to.

Skip it when

  • A declined card at checkout; show the error in the form.

Installation #

terminal
pnpm dlx shadcn@latest add @caliper/payment-recovery

Adds its 3 files to components/ui/, each with its own stylesheet, and installs their packages. Pro installs with your access token, set up once per project. See Pro access. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

Import it from your copy of the source. The example behind the preview comes with the source. It is built from DunningBanner and PaymentRecovery.

example.tsx
import { DunningBanner, PaymentRecovery } from "@/components/ui/payment-recovery"

The worked example comes with Pro

The 43-line example behind the preview opens here with a Pro or Team licence.

Checking your licence…

API reference #

DunningBanner

component
PropTypeDefaultDescription
amount *number—
currencystring"EUR"
reason *string—
attempt *number—Attempts made so far.
maxAttempts *number—
nextRetrystring—
suspendOn *string—When the workspace becomes read-only if nothing is paid.
onUpdate *() => void—
onRetry(() => void)—
onDismiss(() => void)—Only offered before the final attempt.
classNamestring—

PaymentRecovery

component
PropTypeDefaultDescription
invoices *readonly FailedInvoice[]—
attempts *readonly RetryAttempt[]—
suspendOn *string—
deleteOnstring—When data is removed after suspension, if ever.
card{ brand: string; last4: string; }—
currencystring"EUR"
onRetry *() => Promise<void>—Charge again. Throw an Error whose message is the bank's reason to show a failure.
onUpdate *() => void—
contact{ label: string; href: string; }—
classNamestring—

Accessibility #

It exposes status semantics, with aria-hidden, aria-label and aria-labelledby kept in step with state. Changes that matter are announced through a live region.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.