Create account
Sign in
Home
Payments
Business operations
Financial services
Developer tools
Security
All products
Home
Payments
Business operations
Home
Payments
Business operations
Financial services
Developer tools
Support
Overview
How cards work
Sample integration
Accept a payment
More payment scenarios
Set up future payments
Save a card during payment
Place a hold on a card
Finalize payments server-side
3D Secure authentication
Card brand choice
Supported card brands
U.S. and Canadian cards
Testing
payments
·
HomePaymentsOnline paymentsMore payment scenarios

Save a card during payment

Learn how to save card details during a payment.

Use the Payment Intents API to save card details from a purchase. There are several use cases:

  • Charge a customer for an e-commerce order and store the details for future purchases
  • Initiate the first payment of a series of recurring payments
  • Charge a deposit and store the details to charge the full amount later

Set up Stripe
Server-side

First, you need a Stripe account. Register now.

Use our official libraries for access to the Stripe API from your application:

Terminal
# Available as a gem sudo gem install stripe
Gemfile
# If you use bundler, you can add this line to your Gemfile gem 'stripe'

Create a Customer before payment
Server-side

How it works

Check out the sample on saving cards or see the code on GitHub.

To set a card up for future payments, it must be attached to a Customer.

You should create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customer’s account later.

Create or retrieve a Customer to associate with these card details. Include the following code on your server to create a new Customer.

Terminal
curl https://api.stripe.com/v1/customers \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -X "POST"

Associate the ID of the Customer object with your own internal representation of a customer, if you have one.

Create a PaymentIntent
Server-side

Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

To create a payment, specify the amount, currency and customer.

Terminal
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "customer"="{{CUSTOMER_ID}}" \ -d "amount"=1099 \ -d "currency"="usd"

Instead of passing the entire PaymentIntent object to the browser, just pass the client secret. The PaymentIntent’s client secret is a unique key that lets you confirm the payment and update card details on the client, without allowing manipulation of sensitive information, like payment amount.

Collect card details
Client-side

After creating a PaymentIntent on the server and passing its client secret to the browser, you’re ready to collect card information with Stripe Elements on your client. Elements is a set of prebuilt UI components for collecting and validating card number, ZIP code, and expiration date.

Set up Stripe Elements

Include the Stripe.js script by adding it to the head of your checkout page. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Do not include the script in a bundle or host a copy of it yourself.

checkout.html
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head>

Next, create an instance of Elements with the following JavaScript:

script.js
View full sample
// Set your publishable key: remember to change this to your live publishable key in production // See your keys here: https://dashboard.stripe.com/account/apikeys var stripe = Stripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
); var elements = stripe.elements();

Add Elements to your page

Elements needs a place to live in your payment form. Create empty DOM nodes (containers) with unique IDs in your payment form and then pass those IDs to Elements.

checkout.html
View full sample
<div id="card-element"> <!-- Elements will create input elements here --> </div> <!-- We'll put the error messages in this element --> <div id="card-errors" role="alert"></div> <button id="submit">Pay</button>

When the form above has loaded, create an instance of an Element and mount it to the Element container:

client.js
View full sample
// Set up Stripe.js and Elements to use in checkout form var style = { base: { color: "#32325d", } }; var card = elements.create("card", { style: style }); card.mount("#card-element");

The card Element simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card details.

Otherwise, combine cardNumber, cardExpiry, and cardCvc Elements for a flexible, multi-input card form.

Always collect a postal code to increase card acceptance rates and reduce fraud.

The single input card Element automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with multi-input card Elements (cardNumber, cardExpiry, cardCvc), add a separate input field for the customer’s postal code.

Elements validates user input as it is typed. To help your customers catch mistakes, listen to change events on the card Element and display any errors (e.g., postal code validation depends on your customer’s billing country). Use our international test cards to experiment with other postal code formats.

card.on('change', function(event) { var displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } });

A Stripe Element contains an iframe that securely sends the payment information to Stripe over an HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.

You can test your integration without using HTTPS. Enable it when you’re ready to accept live payments.

Submit the payment to Stripe
Client-side

To complete the payment when the user clicks, retrieve the client secret from the PaymentIntent you created in step two and call stripe.confirmCardPayment:

Pass additional billing details, such as the cardholder name and address, to the billing_details hash. The card Element automatically sends the customer’s postal code information. However, combining cardNumber, cardCvc, and cardExpiry Elements requires you to pass the postal code to billing_details[address][postal_code].

client.js
View full sample
stripe.confirmCardPayment(clientSecret, { payment_method: { card: card, billing_details: { name: 'Jenny Rosen' } }, setup_future_usage: 'off_session' }).then(function(result) { if (result.error) { // Show error to your customer console.log(result.error.message); } else { if (result.paymentIntent.status === 'succeeded') { // Show a success message to your customer // There's a risk of the customer closing the window before callback execution // Set up a webhook or plugin to listen for the payment_intent.succeeded event // to save the card to a Customer // The PaymentMethod ID can be found on result.paymentIntent.payment_method } } });

The client secret can be used to complete the payment process with the amount specified on the PaymentIntent. It should not be logged, embedded in URLs, or exposed to anyone other than the customer.

The setup_future_usage parameter helps optimize future payments made with the same card. When both setup_future_usage and a customer are provided on a PaymentIntent, the payment’s card details are automatically saved to the customer once the payment is confirmed. You can also supply this parameter when creating the PaymentIntent on your server.

Supplying an appropriate setup_future_usage value for your application may require your customer to complete additional authentication steps, but reduces the chance of future payments being rejected by the bank. See Optimizing cards for future payments to determine which value you should use for your application.

stripe.confirmCardPayment may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.

When the payment completes successfully, the value of the returned PaymentIntent’s status property is succeeded. If the payment was not successful, you can inspect the returned error to determine the cause.

If the payment completed successfully, the payment’s card is saved to the payment’s customer. This is reflected on the PaymentMethod’s customer field. At this point, associate the ID of the Customer object with your own internal representation of a customer, if you have one. Now you can use the stored PaymentMethod object to collect payments from your customer in the future without prompting them for their payment details again.

Charge the saved card later
Server-side

When you are ready to charge your customer off-session, use the Customer and PaymentMethod IDs to create a PaymentIntent.

To find a card to charge, list the PaymentMethods associated with your Customer.

Terminal
curl https://api.stripe.com/v1/payment_methods \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "customer"="{{CUSTOMER_ID}}" \ -d "type"="card" \ -G

When you have the Customer and PaymentMethod IDs, create a PaymentIntent with the amount and currency of the payment. Set a few other parameters to make the off-session payment:

  • Set off_session to true to indicate that the customer is not in your checkout flow during this payment attempt. This causes the PaymentIntent to throw an error if authentication is required.
  • Set the value of the PaymentIntent’s confirm property to true, which causes confirmation to occur immediately when the PaymentIntent is created.
  • Set payment_method to the ID of the PaymentMethod and customer to the ID of the Customer.
Terminal
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d amount=1099 \ -d currency=usd \ -d customer="{{CUSTOMER_ID}}" \ -d payment_method="{{PAYMENT_METHOD_ID}}" \ -d off_session=true \ -d confirm=true

When a payment attempt fails, the request also fails with a 402 HTTP status code and the status of the PaymentIntent is requires_payment_method. You need to notify your customer to return to your application (e.g., by sending an email or in-app notification) to complete the payment. Check the code of the Error raised by the Stripe API library or check the last_payment_error.decline_code on the PaymentIntent to inspect why the card issuer declined the payment.

If the payment failed due to an authentication_required decline code, use the declined PaymentIntent’s client secret and payment method with confirmCardPayment to allow the customer to authenticate the payment.

script.js
View full sample
// Pass the failed PaymentIntent to your client from your server stripe.confirmCardPayment(intent.client_secret, { payment_method: intent.last_payment_error.payment_method.id }).then(function(result) { if (result.error) { // Show error to your customer console.log(result.error.message); } else { if (result.paymentIntent.status === 'succeeded') { // The payment is complete! } } });

stripe.confirmCardPayment may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.

If the payment failed for other reasons, such as insufficient funds on the card, send your customer to a payment page to enter a new card. You can reuse the existing PaymentIntent to attempt the payment again with the new card details.

Test the integration

By this point you should have an integration that:

  1. Collects card details and makes a payment
  2. Saves the card details to a Customer
  3. Charges the card off-session and has a recovery flow to handle declines and authentication requests

There are several test cards you can use to make sure this integration is ready for production. Use them with any CVC, postal code, and future expiration date.

NumberDescription
Succeeds and immediately processes the payment.
Requires authentication for the initial purchase, but succeeds for subsequent payments (including off-session ones) as long as the card is setup with setup_future_usage.
Requires authentication for the initial purchase, and fails for subsequent payments (including off-session ones) with an authentication_required decline code.
Requires authentication for the initial purchase, but fails for subsequent payments (including off-session ones) with an insufficient_funds decline code.
Always fails (including the initial purchase) with a decline code of insufficient_funds.

For the full list of test cards see our guide on testing.

OptionalRe-collect a CVC

See also

  • Handle post-payment events
Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.
On this page
Set up Stripe
Create a Customer before payment
Create a PaymentIntent
Collect card details
Submit the payment to Stripe
Charge the saved card later
Test the integration
Re-collect a CVC