Skip to content
On this page

Developers › Payments

Save a card

Store a card with the customer’s consent and charge it again later.

Verified 6 November 2026Explain this to your team →

View as Markdown
In beta. Saving a card and charging it later both work, and are tested end to end. What is not yet settled is a permission the bank grants per merchant, and whether the bank’s live system accepts a merchant-initiated charge as we send it: it works in the bank’s sandbox, and has not yet been proved in production. Expect it to work; tell us if a charge is refused, because that is a question for the bank rather than for your code.

Ask to save it

Cards belong to a customer, so create one first. Then set setup_future_usage on the payment the customer is making now.

ts
const customer = await sp.customers.create({
  email: "diver@example.com",
  name: "Reuben Arnold",
});

const intent = await sp.paymentIntents.create({
  amount: 6000,
  currency: "usd",
  customer: customer.id,
  setup_future_usage: "off_session",
});

When the payment succeeds you get a payment_method.attached event, and the card appears on the customer.

Charge it later

ts
const methods = await sp.customers.listPaymentMethods(customer.id);
const card = methods.data[0];

await sp.paymentIntents.create({
  amount: 4500,
  currency: "usd",
  customer: customer.id,
  payment_method: card.id,
  off_session: true,
  confirm: true,
});

A charge to a saved card is always taken in full. Combining confirm: true with capture_method: "manual" is refused with a 400 before anything is created: the bank does not allow a hold on a charge the cardholder is not present for. To hold money, send the customer to a checkout session.

confirm: true needs both customer and payment_method. Without them it is also refused with a 400 (param: customer, or param: payment_method if no card was given) before anything is created — no payment intent and no payment_intent.created event is left behind.

It can still be refused

A card issuer can decline a charge the cardholder is not present for. A decline comes back as a 402 with card_declined and a decline_code, and the payment intent stays at requires_payment_method. The issuer may ask for authentication instead. When that happens the payment reaches requires_action with a URL in next_action — you will need the customer back to complete it. Build for that rather than assuming a saved card always works.

And it can come back processing. That means the charge was sent and the bank’s answer has not reached us yet — the card may already have been charged. Do not charge it again: the payment settles on its own within minutes, to succeeded or requires_payment_method, and you get the webhook for whichever it is.

What we hold

Never a card number. What we store is a token from the bank, the masked number, the brand and the expiry — enough to show the customer which card it is, and nothing that could be used anywhere else.