Save a card and charge it later

A saved card is a payment method that belongs to one customer. The customer enters the card once, in Stripe's card fields on your page, and completes any 3D Secure check their bank asks for. After that, Flint can charge the card without asking for the number again.

How you charge it later depends on whether the customer is there. A one-click repeat purchase runs while they are in your app. An automatic invoice or a subscription renewal runs while they are not, and relies on the authentication they completed when they saved the card.

How saving works#

Saving is its own flow, separate from any payment. Your backend starts it, the browser collects the card, and Flint finishes it when the card processor confirms the setup.

Saving a cardResponse
BrowserYour backendFlintPOST /v1/payment-methods with the customer_ida pending payment method and client_setupaccount_id, publishable_key, and client_secretthe customer enters the card, and stripe.confirmSetup runs any 3D Secure checkpayment_method.saved: the card is active
  1. Your backend sends POST /v1/payment-methods with the customer_id to Flint
  2. Flint returns a pending payment method and client_setup to Your backend
  3. Your backend sends account_id, publishable_key, and client_secret to Browser
  4. Browser sends the customer enters the card, and stripe.confirmSetup runs any 3D Secure check to Browser
  5. Flint sends payment_method.saved: the card is active to Your backend

confirmSetup succeeding in the browser does not make the card chargeable. The card stays pending until Flint receives the processor's confirmation, usually a few seconds later. Only an active card can be charged or made the default.

Note: Payments save a card only when asked

Order payments and payment links keep no card on file, with two exceptions: subscription signup saves the card that starts the subscription, and a buyer can check "Save my details for faster checkout" in hosted checkout. A card saved in checkout can pay only in later checkouts, not subscriptions or automatic invoices; see Cards buyers save in checkout. To keep a card you can charge without the buyer, run a separate save before or after the purchase.

Before you start#

  • A webhook endpoint subscribed to payment_method.saved, payment_method.failed, and payment_method.removed.
  • A page in your app where the customer enters the card. The card fields come from Stripe.js, loaded with keys that Flint returns. You don't need a Stripe account of your own.
  • An API key with the scopes shown on these routes:

Only cards can be saved. ACH debit is a one-time payment and does not keep the bank account. Send an Idempotency-Key on every POST and DELETE so a request that times out can be retried safely. See Idempotency.

Save a card#

  1. Create the customer#

    A saved card always belongs to a customer. If your app already creates Flint customers at signup, reuse that ID.

    cURL
    curl -X POST https://api.withflintpay.com/v1/customers \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Idempotency-Key: customer-ada-001" \
      -d '{
        "name": "Ada Lovelace",
        "email": "ada@example.com"
      }'
    

    Store data.customer_id with the user in your app.

  2. Start the save#

    From your backend, create a payment method for the customer:

    cURL
    curl -X POST https://api.withflintpay.com/v1/payment-methods \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Idempotency-Key: save-card-ada-001" \
      -d '{"customer_id": "cus_1kmn0aExample"}'
    
    Response
    {
      "data": {
        "payment_method": {
          "payment_method_id": "pm_1kmn0aExample",
          "customer_id": "cus_1kmn0aExample",
          "type": "card",
          "status": "pending",
          "usage": "off_session",
          "created_at": "2026-09-22T17:04:05Z",
          "updated_at": "2026-09-22T17:04:05Z"
        },
        "client_setup": {
          "stripe": {
            "account_id": "acct_Example",
            "publishable_key": "pk_test_Example",
            "setup_intent": {
              "stripe_js_call": "confirm_setup",
              "client_secret": "seti_Example_secret_Example"
            }
          }
        }
      }
    }
    

    The payment method exists now, with status pending and no card details. Store payment_method_id and send client_setup.stripe to the browser. The client secret authorizes this one setup and nothing else, so your API key stays on your backend. A retry with the same Idempotency-Key returns the same payment method and client secret instead of starting a second save.

    Warning:

    Saving a card needs card payments enabled for the environment. Until they are, this request fails with CAPABILITY_NOT_REQUESTED or MERCHANT_ONBOARDING_REQUIRED.

  3. Collect the card in the browser#

    Mount the Stripe Payment Element with the client secret, then confirm the setup when the customer submits. The card fields run in Stripe-hosted iframes, so card numbers never reach your servers.

    HTML
    <script src="https://js.stripe.com/v3/"></script>
    
    <form id="card-form">
      <div id="payment-element"></div>
      <button type="submit">Save card</button>
      <div id="card-message" role="alert"></div>
    </form>
    
    JavaScript
    // Your backend runs the previous step and returns data.client_setup.stripe.
    const response = await fetch("/account/cards/setup", { method: "POST" });
    const setup = await response.json();
    
    const stripe = Stripe(setup.publishable_key, {
      stripeAccount: setup.account_id, // required: the setup lives on this account
    });
    const elements = stripe.elements({
      clientSecret: setup.setup_intent.client_secret,
    });
    elements.create("payment").mount("#payment-element");
    
    const message = document.querySelector("#card-message");
    
    document.querySelector("#card-form").addEventListener("submit", async (event) => {
      event.preventDefault();
      message.textContent = "";
    
      const { error, setupIntent } = await stripe.confirmSetup({
        elements,
        confirmParams: { return_url: "https://example.com/account/cards" },
        redirect: "if_required",
      });
    
      if (error) {
        // A refused card or a failed 3D Secure check. The form stays usable.
        message.textContent = error.message;
        return;
      }
    
      if (setupIntent.status === "succeeded") {
        // Flint still shows the card as pending. Wait for it to become active.
        message.textContent = "Saving your card...";
      }
    });
    

    confirmSetup opens the bank's 3D Secure challenge on the page when the bank asks for one. With redirect: "if_required", a card never leaves the page, and return_url is only used by payment methods that redirect.

  4. Wait for the card to become active#

    When the processor confirms the setup, Flint moves the card from pending to active, fills in card, and sends payment_method.saved. Act on either signal:

    • Webhook (recommended). Handle payment_method.saved for this payment_method_id. It arrives even if the customer closed the page right after submitting.
    • Poll. If the customer is watching a "Saving your card" message, read GET /v1/payment-methods/pm_1kmn0aExample about once a second until data.status is active. After 30 seconds, stop polling, tell the customer the card is still being saved, and let the webhook finish the job.
    cURL
    curl https://api.withflintpay.com/v1/payment-methods/pm_1kmn0aExample \
      -H "Authorization: Bearer YOUR_API_KEY"
    
    Response
    {
      "data": {
        "payment_method_id": "pm_1kmn0aExample",
        "customer_id": "cus_1kmn0aExample",
        "type": "card",
        "status": "active",
        "usage": "off_session",
        "card": {
          "brand": "visa",
          "last4": "4242",
          "exp_month": 12,
          "exp_year": 2030
        },
        "created_at": "2026-09-22T17:04:05Z",
        "updated_at": "2026-09-22T17:04:09Z"
      }
    }
    

    card holds what you can show the customer: brand, last4, exp_month, exp_year, and wallet (apple_pay or google_pay) when the card came from a wallet. Flint never returns the card number.

  5. Handle a refused card#

    The bank can refuse the setup, for example because the card is declined or the customer fails the 3D Secure check. confirmSetup returns the error in the browser. Flint moves the card to failed and sends payment_method.failed. For a failed 3D Secure check, failure_code can be authentication_required; other codes include card_declined, insufficient_funds, expired_card, and incorrect_cvc.

    A failed card can still be saved. Keep the form on the page and let the customer fix the details or enter another card: the same client secret accepts another attempt. When one succeeds, the same payment_method_id moves from failed to active and payment_method.saved follows.

    Your webhook handler can therefore see payment_method.failed and then payment_method.saved for one card. Decide from the latest event, or read the card. Each event fires at most once per card, so a second refusal in the same form sends no second payment_method.failed.

Card statuses#

Saved card statusesStartFinal
  • pending moves to active on setup confirmed
  • pending moves to failed on bank refuses
  • failed moves to active on retry succeeds
  • active moves to removed on remove
  • active moves to expired on expiration month passes
  • expired moves to active on expiration date updated
  • pending moves to removed on remove
status on payment method
Final values do not change again
  • pending
    The save started and the processor has not confirmed it, or a buyer saved the card in checkout with a mobile phone number and has not confirmed it yet. Not chargeable. Wait for payment_method.saved.
  • active
    The only status you can charge, make the default, or attach to a subscription or invoice. A card with usage: "on_session" can pay only in checkouts the buyer completes.
  • failed
    The bank refused the setup, or a buyer who saved the card in checkout with a mobile phone number didn't confirm it within 24 hours. Not chargeable. After a refused setup, another attempt in the same form can still make it active, or you can remove it.
  • removedFinal
    Detached from the processor for good. Save a new card instead.
  • expired
    The card's expiration month has passed. Flint excludes it from the default list and rejects it for charges and set-default. If the card's expiration date is updated, its status returns to active.

Treat any status other than active as not chargeable, including values added later.

Cards buyers save in checkout#

Every payment method has a usage that says when Flint may charge it:

usageSaved byCan pay
off_sessionPOST /v1/payment-methods, or a subscription signupAnything: order payments, checkouts, subscriptions, automatic invoices, and the customer's default
on_sessionThe buyer, by checking "Save my details for faster checkout" in hosted checkoutOnly checkouts the buyer completes

A buyer who checks the option saves the card they typed for your business only. Flint saves it after the payment succeeds, for the customer the checkout acts for, and sends payment_method.saved with usage: "on_session". That is the customer you created the session for or, for a guest, the customer whose email the buyer confirmed with a six-digit code Flint emailed them. A typed email alone never saves a card. The next time, checkout emails the buyer a code as soon as they type their email. Once they enter it, checkout lists the cards they saved and pays with one.

A buyer can instead give a US or Canadian mobile phone number with the option: a guest in place of the emailed code, or a buyer who confirmed their email, to save the card with the number. Flint keeps the card for your customer with the buyer's email, as pending, and the buyer confirms it after paying, on the receipt, with a code Flint Pay texts to that number. When the customer existed before the payment, the texted code is enough only if the buyer confirmed the email in the checkout or the number is already the customer's saved number; otherwise the buyer also confirms the email with an emailed code. The card becomes active, with payment_method.saved, once they do. A card not confirmed within 24 hours becomes failed with no event, and no one can pay with it. At the buyer's next checkout, typing their email texts a code to the number.

Each card opens only with the proof it was saved with. A card saved with a number never opens with an emailed code, and a card saved by email never opens with a texted one. A customer has one saved number: a card saved with a new number makes it the customer's number, and the cards saved with the old one are removed, with payment_method.removed for each, since no code can open them anymore. A buyer who lost the phone confirms their email with an emailed code, pays with a card, and saves it with their new number. Save with a mobile phone number covers the API.

The buyer agreed to faster checkout, not to be charged later, so Flint never charges an on_session card without them:

  • A subscription, a subscription payment method change, or an automatic invoice returns PAYMENT_METHOD_ON_SESSION_ONLY for it.
  • set-default returns PAYMENT_METHOD_ON_SESSION_ONLY, because subscriptions and automatic invoices charge the default.
  • Paying an order with it using your API key returns PAYMENT_METHOD_ON_SESSION_ONLY. Only the checkout session's own credential can spend it.

To list only the cards you can charge without the buyer, filter by usage:

cURL
curl "https://api.withflintpay.com/v1/payment-methods?customer_id=cus_1kmn0aExample&usage=off_session" \
  -H "Authorization: Bearer YOUR_API_KEY"

Saving in checkout is on by default. Turn it off with checkout.saved_payment_details. Checkout never offers it when customer accounts are merchant hosted, for invoice, subscription, and return checkouts, or for Apple Pay, Google Pay, ACH debit, and Affirm. To build the same option into your own checkout, see Offer to save the card.

Choose a default card#

Flint never picks a default card for you, not even the customer's first card. Set one explicitly:

cURL
curl -X POST https://api.withflintpay.com/v1/payment-methods/pm_1kmn0aExample/set-default \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: default-card-ada-001"

The card must be active. A pending or failed card returns PAYMENT_METHOD_NOT_ACTIVE, and a card the buyer saved in checkout, with usage: "on_session", returns PAYMENT_METHOD_ON_SESSION_ONLY. The response is the payment method, and the customer now carries default_payment_method_id: "pm_1kmn0aExample". Setting another card replaces the default. No webhook fires for this change.

The default fills in when a request leaves the card out:

ChargeUses the default
New subscription with no payment_method_idYes, at creation. The subscription keeps that card afterward.
Automatic invoice with no collection.payment_method_idYes, when the invoice is issued. The invoice keeps that card afterward.
Order paymentNo. Pass the card in payment_source.

Changing the default does not move existing subscriptions or issued invoices to the new card.

A payment method has no "is default" field. To mark the default in your UI, compare each card with the customer's default_payment_method_id, or read the customer with the card expanded. Expanding needs both customers.read and payments.payment_methods.read.

cURL
curl "https://api.withflintpay.com/v1/customers/cus_1kmn0aExample?expand=default_payment_method" \
  -H "Authorization: Bearer YOUR_API_KEY"

Removing the default card clears default_payment_method_id. Flint does not promote another card, so set a new default if the customer has one.

Charge a saved card#

Pick the path by who is present when the charge runs:

Customer present

Pay an order

A one-click repeat purchase. The customer picks a saved card in your app, sees the total, and confirms. If the bank asks for 3D Secure, they complete it on the page.

Customer not there

Issue an automatic invoice

Usage, an overage, or a balance you bill later. Flint charges the card when you issue the invoice and retries on your schedule if it declines. For the same amount on a schedule, use subscription billing.

The difference is how the charge reaches the bank. Flint sends automatic invoice charges and subscription renewals as charges the customer is not present for, backed by the authentication they completed while saving the card, so the bank can approve them without a challenge. An order payment goes to the bank as a charge with the customer present, and the bank can ask for 3D Secure. If your backend pays orders on a schedule, nobody is there to answer that challenge.

While the customer is present#

Create an order for the customer. With customer_id set, Flint checks that the card you charge belongs to that customer.

cURL
curl -X POST https://api.withflintpay.com/v1/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: order-ada-credits-0922" \
  -d '{
    "customer_id": "cus_1kmn0aExample",
    "line_items": [{
      "name": "Credit pack, 1,000 credits",
      "quantity": 1,
      "unit_price_money": {"amount": 3200, "currency": "USD"},
      "fulfillment": {"requirement": "none"}
    }]
  }'

Show data.settlement_amounts.outstanding_money from the response as the total. When the customer confirms, pay the order with the card they chose:

cURL
curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/pay \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: pay-ada-credits-0922" \
  -d '{
    "action": "pay",
    "payment_source": {"payment_method_id": "pm_1kmn0aExample"},
    "expected_outstanding_money": {"amount": 3200, "currency": "USD"}
  }'
Response
{
  "data": {
    "order": {
      "order_id": "ord_1kmn0aExample",
      "payment_status": "paid",
      "settlement_amounts": {
        "paid_money": {"amount": 3200, "currency": "USD"},
        "outstanding_money": {"amount": 0, "currency": "USD"}
      }
    },
    "payment_attempt": {
      "order_payment_attempt_id": "opat_1kmn0aExample",
      "status": "succeeded",
      "is_resumable": false
    }
  }
}
  • payment_source.payment_method_id must be an active card with usage: "off_session". A pending, failed, or removed card returns INVALID_PAYMENT_SOURCE. A card that belongs to a different customer returns PAYMENT_SOURCE_OWNERSHIP_MISMATCH. A card the buyer saved in checkout returns PAYMENT_METHOD_ON_SESSION_ONLY.
  • expected_outstanding_money is the total the customer approved. If tax, shipping, or a promotion changed the balance since, Flint returns ORDER_CHANGED_REFRESH_REQUIRED without charging. Show the new total and ask again.
  • Order payments never fall back to the default card. Without payment_source, the request fails with PAYMENT_SOURCE_REQUIRED.

Read the result from data.payment_attempt.status, not the HTTP status, because a declined card also returns 200. succeeded means the order is paid. A saved card can still be challenged: the bank may ask for 3D Secure on any charge with the customer present, and the attempt comes back as requires_action. Because the customer is on the page, run the pending action with stripe.handleNextAction and resume the attempt with action: "resume". Finish 3D Secure has the full sequence, and Handle a decline covers failed attempts.

To build the card picker, list the customer's cards and show card.brand, card.last4, and the expiry, with the default preselected when it is still active. Flint does not merge duplicates, so a customer who saves the same card twice has two payment methods. Collapse cards with the same brand, last four digits, and expiry in the picker.

If you would rather not build the picker, a hosted checkout session created for the customer, through the order's customer_id or customer_collection.customer_id, offers that customer's saved cards on the payment page. A session created without a customer offers saved cards only after the buyer confirms their email with a code in checkout; typing the email of an existing customer isn't enough. See Confirm the buyer's email.

When the customer is not there#

Bill the amount with an invoice in automatic collection mode. Flint charges the card as soon as you issue the invoice, sends it as a charge the customer is not present for, and retries on your schedule if it declines.

cURL
curl -X POST https://api.withflintpay.com/v1/invoices \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: invoice-ada-usage-sept" \
  -d '{
    "quick_pay": {
      "customer_id": "cus_1kmn0aExample",
      "line_items": [{
        "name": "API usage, September 2026",
        "quantity": 1,
        "unit_price_money": {"amount": 4870, "currency": "USD"},
        "fulfillment": {"requirement": "none"}
      }]
    },
    "collection": {
      "mode": "automatic",
      "payment_method_id": "pm_1kmn0aExample"
    },
    "payment_due": {"type": "none"}
  }'

The invoice is a draft until you issue it. caller_managed skips Flint's invoice email when your app sends its own receipt:

cURL
curl -X POST https://api.withflintpay.com/v1/invoices/inv_1kmn0aExample/issue \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: issue-ada-usage-sept" \
  -d '{"delivery_mode": "caller_managed"}'
  • The card is chosen at issue. Flint uses collection.payment_method_id, or the customer's default card when you leave it out. With neither, the issue request fails with INVOICE_AUTOPAY_PAYMENT_METHOD_REQUIRED. A card that is not active fails with PAYMENT_METHOD_NOT_ACTIVE.
  • The charge runs moments after issue, not inside the issue response. Watch invoice.paid for success and invoice.payment_failed for a decline. An invoice with a payment schedule charges each installment on its due date instead.
  • Declines retry on your schedule, set in invoices.autopay_retry_policy.retry_day_offsets and fixed for the invoice when it is issued. To try again sooner, call POST /v1/invoices/{invoice_id}/collect with a new Idempotency-Key.
  • A different card can pay an outstanding amount. Pass another active saved card in payment_method_id when calling collect, or ask the customer to pay by card from the hosted invoice. Future automatic charges still use the card selected at issue.

Automatic collection in the invoicing guide covers the attempt history.

Manage saved cards#

List a customer's cards#

cURL
curl "https://api.withflintpay.com/v1/payment-methods?customer_id=cus_1kmn0aExample" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "payment_method_id": "pm_1kmn0aExample",
      "customer_id": "cus_1kmn0aExample",
      "type": "card",
      "status": "active",
      "usage": "off_session",
      "card": {"brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2030},
      "created_at": "2026-09-22T17:04:05Z",
      "updated_at": "2026-09-22T17:04:09Z"
    }
  ]
}

The list returns active cards, newest first. Pass status to list another status, such as status=expired for cards past their expiration month or status=pending to find saves the customer abandoned. Pass usage=off_session to leave out cards buyers saved in checkout, for example in a picker for a subscription or an automatic invoice. Page with page_size (up to 100, default 20) and page_token; the last page has no next_page_token. Leave out customer_id to list every saved card in the environment. See Pagination.

Replace the card on a subscription#

A subscription charges the card it was created with, even after the customer's default changes. To move a subscription to a new card, save the card first, then:

cURL
curl -X POST https://api.withflintpay.com/v1/subscriptions/sub_1kmn0aExample/payment-method \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: sub-ada-new-card-001" \
  -d '{"payment_method_id": "pm_2kmn0aExample"}'

PATCH /v1/subscriptions/{subscription_id} with the same field does the same thing. The card must belong to the subscription's customer, or the request fails with PAYMENT_METHOD_CUSTOMER_MISMATCH. A card that is still pending returns PAYMENT_METHOD_NOT_READY, and a card the buyer saved in checkout returns PAYMENT_METHOD_ON_SESSION_ONLY.

Changing the card does not retry a past_due renewal. Follow up with a payment retry; see When a renewal payment fails.

Remove a card#

cURL
curl -X DELETE https://api.withflintpay.com/v1/payment-methods/pm_1kmn0aExample \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: remove-card-ada-001"
Response
{
  "data": {"success": true}
}

Removing a card:

  • Detaches it from the processor so it can never be charged again, and sets its status to removed.
  • Clears the customer's default_payment_method_id if it pointed at this card.
  • Sends payment_method.removed.
  • Succeeds again, with no second event, if the card is already removed.

A subscription that uses the card blocks removal with PAYMENT_METHOD_HAS_ACTIVE_SUBSCRIPTIONS unless the subscription is canceled. Trialing, paused, and past-due subscriptions all count. Move the subscription to another card or cancel it first.

Open invoices do not block removal, but an automatic invoice issued against the card can no longer collect with it. Void and reissue those invoices for another card.

pending cards can be removed too. Flint does not expire an abandoned save, so it stays pending until you remove it, and a customer deletion request cannot be approved while the customer has any active or pending card.

Card details that change on their own#

When a bank reissues a card, the card network can update the saved card. Flint applies the new brand, last4, exp_month, and exp_year without changing the status or the payment_method_id, and without sending a webhook. Read the card when you display it instead of keeping your own copy of those fields.

Let customers manage their own cards#

If customers manage their cards in an account page you build, call the /v1/me/payment-methods routes with a customer session instead of your API key. They list, read, save, remove, and set a default for the signed-in customer only, and send the same webhooks. Two things differ from the routes above:

  • The save request takes no customer_id. Send the optional type field as you would on the merchant route.
  • GET /v1/me/payment-methods/{payment_method_id} takes no expand, and answers 404 for a card that belongs to another customer. While a new card is pending, poll it until status is active.

Build your own customer account walks through that page. Flint's hosted customer account includes card management with no code.

Webhooks#

  1. What happens: The customer submits the card
  2. Flint sends: payment_method.savedorpayment_method.failed
    Saved when the processor confirms the setup and the card is active. Failed when the bank refuses it.
  3. What happens: The customer tries again in the same form
  4. Flint sends: payment_method.saved
    Same payment_method_id as the earlier payment_method.failed.
  5. What happens: You or the customer remove the card
  6. Flint sends: payment_method.removed
    The card can no longer be charged.
  7. What happens: You issue an automatic invoice
  8. Flint sends: invoice.paidorinvoice.payment_failed
    The charge succeeded, or it declined and waits for a retry.

Every payment_method event carries payment_method_id and customer_id. payment_method.saved adds usage, card_brand, card_last4, card_exp_month, card_exp_year, and card_wallet for wallet cards. payment_method.failed adds failure_code and failure_message when the bank gave a reason. Each event fires at most once per card.

Setting a default card, the passing of a card's expiration month, and a network update to its details send no event. Read the payment method or the customer when you need the current state.

Subscription signup through hosted checkout, embedded checkout, and payment links also saves a card and sends payment_method.saved, so your handler sees cards from every surface. So does a buyer who saves their card in checkout, with usage: "on_session", once they confirm it when they saved it with a mobile phone number. None of these cards become the default.

Test it#

Use a sandbox API key and these cards. Any future expiry and any CVC work.

Card numberWhile savingOne-click order paymentAutomatic invoice
4242 4242 4242 4242Saves.Succeeds.Succeeds.
4000 0025 0000 31553D Secure challenge, then saves.Challenges again with requires_action.Succeeds with no challenge.
4000 0027 6000 31843D Secure challenge, then saves.Challenges with requires_action.Declines because the bank requires authentication.
4000 0000 0000 0341Saves.Declines.Declines, then retries on your schedule.
4000 0000 0000 0002Refused. The card becomes failed with card_declined.Not chargeable.Not chargeable.

In the sandbox 3D Secure dialog, choose Fail to rehearse a refused save, then enter 4242 4242 4242 4242 in the same form to watch the same card move from failed to active. Subscription renewals behave like the automatic invoice column. The test card reference lists every Stripe test card, and Testing covers checkout.

A sandbox sends no texts, so a card saved with a mobile phone number is confirmed with a test code. Give any valid US or Canadian mobile phone number, such as +14155552671, then enter one of these codes where the buyer types the texted code:

CodeResult
000000Wrong code. It counts as a wrong try.
999999CUSTOMER_VERIFICATION_UNAVAILABLE, as when texts can't be checked.
Any other six digitsConfirms the code.

The same codes work for the code texted to a returning buyer. Emailed codes arrive as in live mode.

Errors#

  • HTTP 409
    The account never requested card payments, so it cannot save cards. Check GET /v1/capabilities, then ask Flint support to add accept_card_payments.
  • HTTP 409
    Setting a default, issuing an automatic invoice, or changing a subscription's card needs an active card. Wait for payment_method.saved, or save a new card if this one is expired, failed, or removed.
  • HTTP 400
    An order payment named a card that is not active or does not exist. Use an active card.
  • HTTP 400
    The saved card belongs to a different customer than the order.
  • HTTP 400
    An order payment has no payment_source. Orders never fall back to the default card.
  • HTTP 409
    The order total changed after the customer approved it. Show the new total and ask again.
  • HTTP 400
    An automatic invoice was issued with no card to charge. Pass collection.payment_method_id or set a default card, then issue again.
  • HTTP 400
    A subscription was given a card that is still pending. Wait for payment_method.saved.
  • HTTP 400
    The card belongs to a different customer than the subscription.
  • HTTP 400
    The buyer saved the card in checkout, so it pays only in checkouts they complete. Use a card with usage: "off_session" for subscriptions, automatic invoices, defaults, and order payments with your API key.
  • HTTP 409
    A subscription that is not canceled still uses the card. Move it to another card or cancel it, then remove the card.
  • HTTP 400
    Only card can be saved. Leave type out or send card.
  • HTTP 409
    The customer is being deleted, so new cards cannot be saved or made the default.

Error handling covers the envelope these arrive in.

Common mistakes#

  • Charging the card when the browser confirms. The card is pending until payment_method.saved arrives.
  • Treating payment_method.failed as final. The customer can fix the card in the same form, and the same card becomes active.
  • Assuming the first card becomes the default. Flint never sets a default on its own. Call set-default.
  • Paying orders for a customer who is not there. An order payment is sent with the customer present, and a 3D Secure request has nobody to answer it. Use an automatic invoice or a subscription.
  • Expecting a new default to move existing charges. Subscriptions and issued invoices keep their own card.
  • Offering every saved card for a subscription. Cards buyers saved in checkout have usage: "on_session" and are refused. List with usage=off_session.
  • Leaving abandoned saves behind. A pending card never expires, and it blocks a customer deletion request until you remove it.

Next steps#

Was this helpful?