Save a card during 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 StripeServer-side
First, you need a Stripe account. Register now.
Use our official libraries for access to the Stripe API from your application:
# Available as a gem sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'
Create a Customer before paymentServer-side
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.
curl https://api.stripe.com/v1/customers \ -u
: \ -X "POST"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Associate the ID of the Customer object with your own internal representation of a customer, if you have one.
Create a PaymentIntentServer-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.
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -d "customer"="{{CUSTOMER_ID}}" \ -d "amount"=1099 \ -d "currency"="usd"sk_test_4eC39HqLyjWDarjtT1zdp7dc
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 detailsClient-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.
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head>
Next, create an instance of Elements with the following JavaScript:
// 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(
); var elements = stripe.elements();'pk_test_TYooMQauvdEDq54NiTphI7jx'
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.
<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:
// 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 StripeClient-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]
.
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 laterServer-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.
curl https://api.stripe.com/v1/payment_methods \ -u
: \ -d "customer"="{{CUSTOMER_ID}}" \ -d "type"="card" \ -Gsk_test_4eC39HqLyjWDarjtT1zdp7dc
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.
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -d amount=1099 \ -d currency=usd \ -d customer="{{CUSTOMER_ID}}" \ -d payment_method="{{PAYMENT_METHOD_ID}}" \ -d off_session=true \ -d confirm=truesk_test_4eC39HqLyjWDarjtT1zdp7dc
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.
// 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:
- Collects card details and makes a payment
- Saves the card details to a Customer
- 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.
Number | Description |
---|---|
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.