KQ-1772 · FAILED/CHARGE
Failed Charge
An in-app banner for a payment that didn't go through, fixable in place: Retry tries the charge again and Update card opens a short form of saved methods or a new card that saves and retries in one go. While a retry is pending a tinted sweep fills across the banner on a long decelerating tween; when the charge lands it completes on springs.glide, the band turns from muted red to green, the check draws on springs.flick and the banner folds itself away on glide, while a refusal drains the sweep on the exit ease and simply states the reason. The message is a polite live region, the form is a radiogroup of methods (arrows) with labelled inputs, Enter submits and Escape closes it back to Update card.
declined · 1 attempt
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/failed-charge
Props
- invoice
- FailedChargeInvoice
- default INV-2026-10, $284.00, October
- The invoice that failed: { id, number, amount, label }.
- method
- FailedChargeMethod
- default card ending 4417
- The method the charge was tried on: { id, label, kind }; the label reads mid-sentence.
- methods
- FailedChargeMethod[]
- default a bank account ending 0921
- Other saved methods to choose from in the update.
- graceDays
- number
- default 7
- Days the account stays active after the first failure.
- failedAt
- number
- default Oct 15 2026
- When the charge first failed, in ms.
- now
- number
- default Oct 17 2026, 14:00 UTC
- The moment grace is counted at, in ms.
- reason
- string
- default "The card was declined."
- Why the first attempt failed.
- attempts / defaultAttempts
- number
- default — / 1
- Failed attempts so far, controlled or initial; two or more shows the grace days.
- onRetry
- (methodId) => void | Promise
- default —
- Retry was pressed (or a new method was saved); resolve when paid, reject with the reason.
- onUpdateMethod
- (choice) => void | Promise
- default —
- The update form was submitted with a saved method or a new card's fields; resolve with the method to retry on.
- open / defaultOpen
- boolean
- default — / true
- Whether the banner is showing, controlled or initial.
- onDismiss
- (why: "user" | "paid") => void
- default —
- The banner folded away: dismissed by the reader, or paid.
- expanded / defaultExpanded
- boolean
- default — / false
- Whether the update form is open, controlled or initial.
- onExpandedChange
- (open) => void
- default —
- Fires from Update card, Cancel or Escape with the new state.
- foldDelay
- number
- default 1600
- How long Payment received stays before the banner folds, in ms.
- bandStyle
- "edge" | "wash" | "outline"
- default "edge"
- Where the colour sits: a leading band, the whole banner, or the border.
- cornerRadius
- number
- default 10
- Corner radius in px.
- tone
- "soft" | "strong" | "mono"
- default "soft"
- How loud the colour is.
- account
- string
- default "Fernworks"
- The product that stays active through the grace days.
- formatMoney
- (amount) => string
- default Intl USD
- How money is written.
- className
- string
- default —
- Classes for the region that holds the banner.
| Prop | Type | Default | Description |
|---|---|---|---|
| invoice | FailedChargeInvoice | INV-2026-10, $284.00, October | The invoice that failed: { id, number, amount, label }. |
| method | FailedChargeMethod | card ending 4417 | The method the charge was tried on: { id, label, kind }; the label reads mid-sentence. |
| methods | FailedChargeMethod[] | a bank account ending 0921 | Other saved methods to choose from in the update. |
| graceDays | number | 7 | Days the account stays active after the first failure. |
| failedAt | number | Oct 15 2026 | When the charge first failed, in ms. |
| now | number | Oct 17 2026, 14:00 UTC | The moment grace is counted at, in ms. |
| reason | string | "The card was declined." | Why the first attempt failed. |
| attempts / defaultAttempts | number | — / 1 | Failed attempts so far, controlled or initial; two or more shows the grace days. |
| onRetry | (methodId) => void | Promise | — | Retry was pressed (or a new method was saved); resolve when paid, reject with the reason. |
| onUpdateMethod | (choice) => void | Promise | — | The update form was submitted with a saved method or a new card's fields; resolve with the method to retry on. |
| open / defaultOpen | boolean | — / true | Whether the banner is showing, controlled or initial. |
| onDismiss | (why: "user" | "paid") => void | — | The banner folded away: dismissed by the reader, or paid. |
| expanded / defaultExpanded | boolean | — / false | Whether the update form is open, controlled or initial. |
| onExpandedChange | (open) => void | — | Fires from Update card, Cancel or Escape with the new state. |
| foldDelay | number | 1600 | How long Payment received stays before the banner folds, in ms. |
| bandStyle | "edge" | "wash" | "outline" | "edge" | Where the colour sits: a leading band, the whole banner, or the border. |
| cornerRadius | number | 10 | Corner radius in px. |
| tone | "soft" | "strong" | "mono" | "soft" | How loud the colour is. |
| account | string | "Fernworks" | The product that stays active through the grace days. |
| formatMoney | (amount) => string | Intl USD | How money is written. |
| className | string | — | Classes for the region that holds the banner. |