# How to charge a saved card using Stripe

- Tool: Stripe
- Difficulty: Beginner
- Time required: 15 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## TL;DR

Charge a saved card by creating a PaymentIntent with the customer ID and payment_method ID, setting off_session to true and confirm to true. Stripe attempts the charge without customer interaction. Handle 'requires_action' for cards that need 3D Secure by notifying the customer to return and authenticate.

## Reusing Payment Methods for Repeat Charges

Many businesses need to charge customers without them being present — reorders, usage-based billing, or manual invoices. Stripe lets you save a customer's payment method during their first purchase, then charge it later by referencing the customer and payment_method IDs. The key is setting off_session: true so Stripe knows the customer is not actively on your site, and using the right error handling for cards that require authentication.

## Before you start

- A Stripe account with test API keys
- Node.js 18+ with the stripe npm package installed
- A Stripe Customer already created (stripe.customers.create())
- A saved PaymentMethod attached to that customer

## Step-by-step guide

### 1. Save the payment method during initial checkout

When the customer first pays, use a Checkout Session or SetupIntent with setup_future_usage or mode: 'setup' to save their card. This stores the payment method for later use.

```
// Option 1: Save during Checkout
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  customer: customerId,
  payment_intent_data: {
    setup_future_usage: 'off_session',
  },
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
});

// Option 2: Save via SetupIntent (no charge)
const setupIntent = await stripe.setupIntents.create({
  customer: customerId,
  automatic_payment_methods: { enabled: true },
});
```

**Expected result:** The payment method is saved and attached to the customer. You can see it in Dashboard → Customers → [Customer] → Payment methods.

### 2. List saved payment methods

Before charging, you may want to list the customer's saved payment methods to let them pick one or to grab the default.

```
const paymentMethods = await stripe.paymentMethods.list({
  customer: customerId,
  type: 'card',
});

console.log(paymentMethods.data);
// Each entry has: id, card.brand, card.last4, card.exp_month, card.exp_year
```

**Expected result:** An array of saved payment methods with card details (brand, last4, expiry).

### 3. Charge the saved card off-session

Create a PaymentIntent with the customer ID, payment method ID, off_session: true, and confirm: true. Stripe attempts the charge immediately without the customer present.

```
async function chargeSavedCard(customerId, paymentMethodId, amount) {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: amount, // in cents
      currency: 'usd',
      customer: customerId,
      payment_method: paymentMethodId,
      off_session: true,
      confirm: true,
    });

    return { success: true, paymentIntent };
  } catch (err) {
    if (err.code === 'authentication_required') {
      // Card requires 3D Secure — notify customer to authenticate
      return {
        success: false,
        requiresAuth: true,
        paymentIntentId: err.raw.payment_intent.id,
      };
    }
    throw err;
  }
}
```

**Expected result:** For most saved cards, the PaymentIntent immediately transitions to 'succeeded'. For cards requiring auth, you get an authentication_required error.

### 4. Handle authentication-required cases

When a saved card requires 3D Secure, send the customer an email or notification with a link to complete authentication. On that page, use stripe.confirmPayment() to let them authenticate.

```
// Send customer to an authentication page with the PaymentIntent ID
// On the authentication page:
const { error } = await stripe.confirmPayment({
  clientSecret: paymentIntent.client_secret,
  confirmParams: {
    return_url: 'https://yoursite.com/payment-authenticated',
  },
});
```

**Expected result:** The customer completes 3D Secure and the payment succeeds.

## Complete code example

File: `charge-saved-card.js`

```javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

async function chargeSavedCard(customerId, paymentMethodId, amountInCents) {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: amountInCents,
      currency: 'usd',
      customer: customerId,
      payment_method: paymentMethodId,
      off_session: true,
      confirm: true,
      metadata: {
        charge_type: 'saved_card_recharge',
      },
    });

    console.log(`Payment succeeded: ${paymentIntent.id}`);
    return { success: true, paymentIntentId: paymentIntent.id };
  } catch (err) {
    if (err.code === 'authentication_required') {
      console.log('Authentication required — notify customer');
      return {
        success: false,
        requiresAuth: true,
        paymentIntentId: err.raw.payment_intent.id,
        clientSecret: err.raw.payment_intent.client_secret,
      };
    }

    if (err.code === 'card_declined') {
      console.log('Card declined — ask customer to update payment method');
      return { success: false, declined: true, message: err.message };
    }

    console.error('Unexpected error:', err.message);
    throw err;
  }
}

async function listSavedCards(customerId) {
  const methods = await stripe.paymentMethods.list({
    customer: customerId,
    type: 'card',
  });
  return methods.data.map((pm) => ({
    id: pm.id,
    brand: pm.card.brand,
    last4: pm.card.last4,
    expMonth: pm.card.exp_month,
    expYear: pm.card.exp_year,
  }));
}

module.exports = { chargeSavedCard, listSavedCards };
```

## Common mistakes

- **Forgetting to set off_session: true for server-initiated charges** — undefined Fix: Without off_session: true, Stripe may require authentication which cannot happen without the customer present. Always set this flag for saved-card charges.
- **Not saving the payment method during initial checkout** — undefined Fix: Use setup_future_usage: 'off_session' on the PaymentIntent or Checkout Session during the first payment. Without this, the payment method is not reusable.
- **Not handling the authentication_required error** — undefined Fix: Some cards require 3D Secure even for saved cards. Catch this error and send the customer a link to complete authentication.
- **Storing card numbers instead of using Stripe PaymentMethod IDs** — undefined Fix: Never store raw card numbers. Save the Stripe PaymentMethod ID (pm_xxx) and Customer ID (cus_xxx). Stripe handles PCI compliance for you.

## Best practices

- Use setup_future_usage: 'off_session' during the initial payment to optimize for future off-session charges
- Always handle authentication_required errors — send the customer a notification to authenticate
- Store PaymentMethod IDs (pm_xxx) in your database, never raw card numbers
- Test off-session charges with card 4242 4242 4242 4242 (succeeds) and 4000 0025 0000 3155 (requires auth)
- Set up webhooks for payment_intent.succeeded and payment_intent.payment_failed to track results
- Display saved cards to the customer with brand and last4 digits so they can choose which to charge
- Handle card_declined errors gracefully — prompt the customer to update their payment method

## Frequently asked questions

### Can I charge a saved card without the customer being on my site?

Yes. Set off_session: true and confirm: true when creating the PaymentIntent. Stripe charges the card immediately. However, some cards may require authentication — handle this by notifying the customer.

### How long does a saved payment method last?

A saved PaymentMethod stays valid until the card expires or the customer removes it. Stripe automatically updates cards through its Account Updater service when banks issue new card numbers.

### What happens if the saved card is expired?

The charge fails with a card_declined error code. Notify the customer to update their payment method. Stripe's Account Updater often prevents this by auto-updating card details.

### Do I need to be PCI compliant to save cards?

No. Stripe handles card storage and PCI compliance. You only store the PaymentMethod ID (pm_xxx), which is a token — not the actual card number.

### Can RapidDev help me build a saved-card payment system?

Yes. RapidDev can architect a complete saved-card system including the initial card-saving flow, recurring charge logic, failed-payment retry strategies, and customer payment-method management UI.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-charge-a-saved-card-using-stripe
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-charge-a-saved-card-using-stripe
