Skip to content

Saved Cards (Recurring Payments)

Save a customer's card once, then charge it repeatedly without user interaction — ideal for subscriptions, installments, or one-click checkout.

How It Works

flowchart TB
    subgraph "First Time (card collection)"
        A["Frontend collects card<br/>via Stripe.js"] --> B["POST /v1/payments/cards<br/>{ payment_method_id: 'pm_...' }"]
        B --> C["API creates Stripe Customer<br/>+ attaches card"]
        C --> D["Returns { last4, brand, id }"]
    end

    subgraph "Recurring Charges"
        E["POST /v1/payments<br/>{ saved_card_id: 'card-uuid' }"] --> F["API charges off-session<br/>using stored pm_ + cus_"]
        F --> G["Payment completed<br/>without user interaction"]
    end

    D --> E

Step 1: Collect Card (Frontend)

Use Stripe.js or Stripe Elements to collect the card and create a PaymentMethod token:

const stripe = Stripe('pk_live_...');
const { paymentMethod } = await stripe.createPaymentMethod({
  type: 'card',
  card: cardElement,
});
// paymentMethod.id = "pm_1abc..."

Never send raw card numbers to your server

Always use Stripe.js to tokenize. Your server only receives the pm_ token.

Step 2: Save the Card

POST /v1/payments/cards
X-API-Key: sk_live_...
{
  "payment_method_id": "pm_1abc...",
  "customer_email": "jane@example.com",
  "customer_name": "Jane Doe"
}

Response

{
  "success": true,
  "data": {
    "id": "card-uuid-...",
    "provider": "stripe",
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2027,
    "is_default": true,
    "created_at": "2025-01-01T12:00:00Z"
  }
}

Step 3: Charge the Saved Card

POST /v1/payments
X-API-Key: sk_live_...
Idempotency-Key: subscription-jan-2025
{
  "amount": 29.99,
  "currency": "USD",
  "provider": "stripe",
  "saved_card_id": "card-uuid-..."
}

The payment is processed off-session with customer + payment_method set on the PaymentIntent. No 3DS prompt, no user interaction.

Managing Cards

# List all saved cards
GET /v1/payments/cards

# Delete a card (detaches from Stripe + deactivates locally)
DELETE /v1/payments/cards/{card_id}

Security

Layer Protection
Card collection Stripe.js — raw numbers never touch your server
Storage Only pm_ token, brand, last4, expiry stored — no card numbers
Encryption Merchant's Stripe credentials encrypted at rest (Fernet)
Off-session Stripe handles SCA exemptions for merchant-initiated payments