Payment methods
Cards used for Northwind subscription invoices.
Pay the next invoice with
The default card can’t be removed. Make another card the default first.
If a payment fails
saved-payment-methods.tsx
"use client";
import { useState } from "react";
import { LuPlus, LuTrash2 } from "react-icons/lu";
import { Button, CardForm, Modal, Radio, RadioGroup, SavedCard } from "@nexera-ui/react";
interface Card {
id: string;
last4: string;
label: string;
subtitle: string;
status?: "expired" | "frozen";
}
/** A fixed "today", so the expiry check gives the same result on the server and in the browser. */
const TODAY = new Date(2026, 9, 7);
const INITIAL: Card[] = [
{ id: "c1", last4: "4242", label: "Company card", subtitle: "Expires 09/29 · Sara Malik" },
{ id: "c2", last4: "5556", label: "Travel card", subtitle: "Expires 02/28 · Mia Jensen" },
{
id: "c3",
last4: "0005",
label: "Card",
subtitle: "Expired 03/26 · Sara Malik",
status: "expired",
},
];
/** Saved payment methods: choose the card for the next invoice, set the default, add or remove a card. */
export default function SavedPaymentMethods() {
const [cards, setCards] = useState(INITIAL);
const [selected, setSelected] = useState("c1");
const [defaultId, setDefaultId] = useState("c1");
const [adding, setAdding] = useState(false);
const [fallback, setFallback] = useState("retry");
const current = cards.find((c) => c.id === selected);
const remove = () => {
const rest = cards.filter((c) => c.id !== selected);
setCards(rest);
setSelected(defaultId);
};
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid w-full max-w-2xl gap-6 rounded-xl border border-default bg-surface p-5 shadow-xs @3xl:p-6">
<header className="flex flex-wrap items-start justify-between gap-3">
<div className="grid gap-1">
<h2 className="text-heading-h3 text-primary">Payment methods</h2>
<p className="text-body-default text-secondary">
Cards used for Northwind subscription invoices.
</p>
</div>
<Modal
title="Add a card"
subtitle="The card is charged on the 1st of each month."
isOpen={adding}
onOpenChange={setAdding}
trigger={
<Button variant="secondary" leadingIcon={<LuPlus />}>
Add card
</Button>
}
>
<CardForm
today={TODAY}
onSubmit={(values) => {
const id = `c${String(cards.length + 1)}-${values.number.slice(-4)}`;
setCards([
...cards,
{
id,
last4: values.number.slice(-4),
label: "Card",
subtitle: `Expires ${values.expiry} · ${values.name}`,
},
]);
setSelected(id);
setAdding(false);
}}
>
<p className="text-body-small text-secondary">
Use the test number 4242 4242 4242 4242.
</p>
<Button type="submit" size="md">
Save card
</Button>
</CardForm>
</Modal>
</header>
<RadioGroup
label="Pay the next invoice with"
type="card"
value={selected}
onChange={setSelected}
name="payment-method"
>
{cards.map((card) => (
<SavedCard
key={card.id}
value={card.id}
title={`${card.label} ending ${card.last4}`}
subtitle={card.subtitle}
{...(card.status ? { status: card.status } : {})}
{...(card.id === defaultId ? { defaultBadge: "Default" } : {})}
/>
))}
</RadioGroup>
<div className="flex flex-wrap gap-2">
<Button
variant="secondary"
isDisabled={!current || current.id === defaultId || current.status !== undefined}
onPress={() => {
setDefaultId(selected);
}}
>
Make default
</Button>
<Button
variant="destructive"
leadingIcon={<LuTrash2 />}
isDisabled={!current || current.id === defaultId}
onPress={remove}
>
{current ? `Remove card ending ${current.last4}` : "Remove card"}
</Button>
</div>
<p className="text-body-small text-muted">
The default card can’t be removed. Make another card the default first.
</p>
<RadioGroup
label="If a payment fails"
value={fallback}
onChange={setFallback}
name="fallback"
className="border-t border-subtle pt-5"
>
<Radio value="retry">Retry the default card after 3 days</Radio>
<Radio value="next">Try the next saved card straight away</Radio>
<Radio value="invoice">Email an invoice to finance@northwind.io</Radio>
</RadioGroup>
</div>
</div>
);
}
More commerce and payments blocks
- Pricing plansMonthly or yearly plans with a recommended one.
- CheckoutCard details, billing address and order total.
- Order summaryItems, discounts and totals.
- PayslipEarnings and deductions with totals.
- Product galleryPhotos with thumbnails and zoom.
- Reviews and ratingsAverage score, breakdown and written reviews.
- Order trackingWhere an order is, step by step.