Payment recovery
ProWhen 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.
pnpm dlx shadcn@latest add @caliper/payment-recoveryWe 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 Oct · Declined
- 4 Oct · Declined
- 13 Oct · Automatic retry
- 20 Oct · Automatic retry
- 24 Oct · Workspace becomes read-only. People can sign in and export, but not edit.
- 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 #
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.
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.
API reference #
DunningBanner
component
PaymentRecovery
component
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.