Saving Card Details
Learn how to save card details and comply with regulations like Strong Customer Authentication.
Collect payments from a customer after they have left your application by attaching payment methods to a Customer object. Depending on how you intend to collect payments from customers in the future, you may benefit from better acceptance rates when you use the Payment Intents and Setup Intents APIs to collect card details.
If your business is impacted by Strong Customer Authentication (SCA), follow this guide to reduce the chance your customer needs to return to your application to authenticate future payments. However, even if your business is not impacted by SCA, you can still benefit from better acceptance rates when charging cards that require authentication.
When you collect card details as part of your integration, Stripe can optimize your payment flow to only require authentication when it is legally mandated, or if it improves acceptance rates significantly for the chosen card.
Saving card details after a payment
Use the Payment Intents API to charge a card and save the details for future payments. Common scenarios include:
- Charging a customer for an e-commerce order and storing the details for future purchases
- Saving a card when a customer creates an account with your service, even if you’re not sure whether they will make another payment in the future
- Initiating the first payment of a series of recurring payments
Saving card details after a payment consists of the following steps:
Step 1: Create a PaymentIntent on the server
There are two ways to accept payments with the Payment Intents API: automatic confirmation and manual confirmation. Automatic confirmation completes the payment on the client and relies on webhooks to fulfill any purchases, while manual confirmation lets you finish the payment on the server and immediately run any post-payment logic. Read more about automatic and manual confirmation.
A PaymentIntent is an object that represents your intent to collect a payment from a customer, tracking the lifecycle of the payment process through each stage. To initiate a payment while enabling future payments using the same card details, specify the following parameters:
Parameter | Description |
---|---|
amount (integer) | The amount of money to collect from your customer |
currency (string) | The type of currency to collect from your customer |
setup_future_usage (enum) | How you intend to collect payments from your customer in the future |
Setting setup_future_usage
allows Stripe to optimize the authentication process for future payments with the same payment method. Authenticating up front can improve acceptance rates for future payments. To determine which value to use, consider how you want to use this payment method in the future.
How you intend to use the card | setup_future_usage enum value to use |
---|---|
On-sessionA payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method. payments only | on_session |
Off-sessionA payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information. payments only | off_session |
Both on and off-session payments | off_session |
setup_future_usage
is an optimization. A card set up for on-session payments can still be used to make off-session payments, but there’s a higher likelihood that the bank will reject the off-session payment and require authentication from the cardholder.
The following example shows how to create a PaymentIntent on your server while also specifying setup_future_usage
. For manual confirmation, also set confirmation_method
to manual
.
curl https://api.stripe.com/v1/payment_intents \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d amount=1099 \
-d currency=usd \
-d setup_future_usage=off_session
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
Stripe::PaymentIntent.create(
amount: 1099,
currency: 'usd',
setup_future_usage: 'off_session',
)
stripe.PaymentIntent.create(
amount=1099,
currency='usd',
setup_future_usage='off_session',
)
$intent = \Stripe\PaymentIntent::create([
'amount' => 1099,
'currency' => 'usd',
'setup_future_usage' => 'off_session',
])
Map<String, Object> params = new HashMap<>();
params.put("amount", 1099);
params.put("currency", "eur");
params.put("setup_future_usage", "off_session");
PaymentIntent paymentIntent = PaymentIntent.create(params);
(async () => {
const intent = await stripe.paymentIntents.create({
amount: 1099,
currency: 'usd',
setup_future_usage: 'off_session',
});
})();
params := &stripe.PaymentIntentParams{
Amount: stripe.Int64(1099),
Currency: stripe.String(string(stripe.CurrencyUSD)),
SetupFutureUsage: stripe.String(string(stripe.PaymentIntentSetupFutureUsageOffSession)),
}
intent, err := paymentintent.New(params)
var options = new PaymentIntentCreateOptions
{
Amount = 1099,
Currency = "usd",
SetupFutureUsage = "off_session",
};
var service = new PaymentIntentService();
PaymentIntent paymentIntent = service.Create(options);
Step 2: Pass the PaymentIntent’s client secret to the client
The PaymentIntent object contains a client secret, a unique key that you need to pass to Stripe.js on the client side in order to create a charge. If your application uses server-side rendering, use your template framework to embed the client secret in the page using a data attribute or a hidden HTML element.
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="<%= @intent.client_secret %>">Submit Payment</button>
get '/checkout' do
@intent = # ... Fetch or create the PaymentIntent
erb :checkout
end
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
Submit Payment
</button>
@app.route('/checkout')
def checkout():
intent = # ... Fetch or create the PaymentIntent
return render_template('checkout.html', client_secret=intent.client_secret)
<?php
$intent = # ... Fetch or create the PaymentIntent;
?>
...
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="<?= $intent->client_secret ?>">
Submit Payment
</button>
...
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
Submit Payment
</button>
import java.util.HashMap;
import java.util.Map;
import com.stripe.model.PaymentIntent;
import spark.ModelAndView;
import static spark.Spark.get;
public class StripeJavaQuickStart {
public static void main(String[] args) {
get("/checkout", (request, response) -> {
PaymentIntent intent = // ... Fetch or create the PaymentIntent
Map<String, String> map = new HashMap();
map.put("client_secret", intent.getClientSecret());
return new ModelAndView(map, "checkout.hbs");
}, new HandlebarsTemplateEngine());
}
}
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
Submit Payment
</button>
const express = require('express');
const expressHandlebars = require('express-handlebars');
const app = express();
app.engine('.hbs', expressHandlebars({ extname: '.hbs' }));
app.set('view engine', '.hbs');
app.set('views', './views');
app.get('/checkout', async (req, res) => {
const intent = // ... Fetch or create the PaymentIntent
res.render('checkout', { client_secret: intent.client_secret });
});
app.listen(3000, () => {
console.log('Running on port 3000')
});
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ .ClientSecret }}">
Submit Payment
</button>
package main
import (
"html/template"
"net/http"
stripe "github.com/stripe/stripe-go"
)
type CheckoutData struct {
ClientSecret string
}
func main() {
checkoutTmpl := template.Must(template.ParseFiles("views/checkout.html"))
http.HandleFunc("/checkout", func(w http.ResponseWriter, r *http.Request) {
intent := // ... Fetch or create the PaymentIntent
data := CheckoutData{
ClientSecret: intent.ClientSecret,
}
checkoutTmpl.Execute(w, data)
})
http.ListenAndServe(":3000", nil)
}
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret='@ViewData["ClientSecret"]'>
Submit Payment
</button>
using System;
using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace StripeExampleApi.Controllers
{
[Route("/[controller]")]
public class CheckoutController : Controller
{
public IActionResult Index()
{
var intent = // ... Fetch or create the PaymentIntent
ViewData["ClientSecret"] = intent.ClientSecret;
return View();
}
}
}
Each PaymentIntent typically correlates with a single “cart” or customer session in your application. You can create the PaymentIntent during checkout and store its ID on the user’s cart in your application’s data model, retrieving it again as necessary.
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. Make sure that you have TLS enabled on any page that includes the client secret.
Step 3: Collect payment method details on the client
The Payment Intents API is fully integrated with Stripe.js, using Elements to securely collect payment information on the client side and submitting it to Stripe to create a charge. To get started with Elements, include the following script on your pages. This script must always load directly from js.stripe.com in order to remain PCI compliantAny party involved in processing, transmitting, or storing credit card data must comply with the rules specified in the the Payment Card Industry (PCI) Data Security Standards. PCI compliance is a shared responsibility and applies to both Stripe and your business.—you can’t include it in a bundle or host a copy of it yourself.
<script src="https://js.stripe.com/v3/"></script>
To best leverage Stripe’s advanced fraud functionality, include this script on every page on your site, not just the checkout page. Including the script on every page allows Stripe to detect anomalous behavior that may be indicative of fraud as users browse your website.
Next, create an instance of the Stripe object, providing your publishable API key as the first parameter. Afterwards, create an instance of the Elements object and use it to mount a Card element in the relevant placeholder in the page.
var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
var elements = stripe.elements();
var cardElement = elements.create('card');
cardElement.mount('#card-element');
const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
Step 4: Submit the payment to Stripe from the client
To complete the payment, retrieve the client secret made available in the second step. After obtaining the client secret from the data attribute, use stripe.handleCardPayment to complete the payment:
var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');
var clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', function(ev) {
stripe.handleCardPayment(
clientSecret, cardElement, {
payment_method_data: {
billing_details: {name: cardholderName.value}
}
}
).then(function(result) {
if (result.error) {
// Display error.message in your UI.
} else {
// The payment has succeeded. Display a success message.
}
});
});
const cardholderName = document.getElementById('cardholder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (ev) => {
const {paymentIntent, error} = await stripe.handleCardPayment(
clientSecret, cardElement, {
payment_method_data: {
billing_details: {name: cardholderName.value}
}
}
);
if (error) {
// Display error.message in your UI.
} else {
// The payment has succeeded. Display a success message.
}
});
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 is not successful, inspect the returned error
to determine the cause.
Fulfill the customer’s order asynchronously
You can use the PaymentIntent returned by Stripe.js to provide immediate feedback to your customers when the payment completes on the client. However, your integration should not attempt to handle order fulfillmentFulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected. on the client side because it is possible for customers to leave the page after payment is complete but before the fulfillment process initiates. Instead, you will need to handle asynchronous events in order to be notified and drive fulfillment when the payment succeeds.
There are several ways you can fulfill an order:
- Setting up a webhook
- Manually using the Stripe Dashboard
- Using a partner application
- Using a cron job to fetch recent events
To complete the payment, retrieve the client secret made available in the second step. After obtaining the client secret from the data attribute, use stripe.handleCardAction to complete the payment:
function handleServerResponse(response) {
if (response.error) {
// Show error from server on payment form
} else if (response.requires_action) {
// Use Stripe.js to handle required card action
stripe.handleCardAction(
response.payment_intent_client_secret
).then(function(result) {
if (result.error) {
// Show error in payment form
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
fetch('/ajax/confirm_payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_intent_id: result.paymentIntent.id })
}).then(function(confirmResult) {
return confirmResult.json();
}).then(handleServerResponse);
}
});
} else {
// Show success message
}
}
const handleServerResponse = async (response) => {
if (response.error) {
// Show error from server on payment form
} else if (response.requires_action) {
// Use Stripe.js to handle the required card action
const { error: errorAction, paymentIntent } =
await stripe.handleCardAction(response.payment_intent_client_secret);
if (errorAction) {
// Show error from Stripe.js in payment form
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
const serverResponse = await fetch('/ajax/confirm_payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_intent_id: paymentIntent.id })
});
handleServerResponse(await serverResponse.json());
}
} else {
// Show success message
}
}
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 requires_confirmation
. Confirm the PaymentIntent again on your server to complete the payment. If the payment was not successful, inspect the returned error
to determine the cause.
Step 5: Attach the PaymentMethod to a Customer after success
When creating a new Customer, pass a PaymentMethod ID to immediately add a payment method.
curl https://api.stripe.com/v1/customers \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d payment_method="{{PAYMENT_METHOD_ID}}"
# This creates a new Customer and attaches the PaymentMethod in one API call.
customer = Stripe::Customer.create(payment_method: intent.payment_method)
# This creates a new Customer and attaches the PaymentMethod in one API call.
stripe.Customer.create(
payment_method=intent.payment_method
)
// This creates a new Customer and attaches the PaymentMethod in one API call.
\Stripe\Customer::create([
'payment_method' => $intent->payment_method,
]);
// This creates a new Customer and attaches the PaymentMethod in one API call.
Map<String, Object> customerParams = new HashMap<String, Object>();
customerParams.put("payment_method", intent.getPaymentMethod());
Customer.create(customerParams);
// This creates a new Customer and attaches the PaymentMethod in one API call.
const customer = await stripe.customers.create({
payment_method: intent.payment_method,
});
// This creates a new Customer and attaches the PaymentMethod in one API call.
customerParams := &stripe.CustomerParams{
PaymentMethod: intent.PaymentMethod.ID,
}
c, err := customer.New(customerParams)
var options = new CustomerCreateOptions {
PaymentMethodId = intent.PaymentMethodId
};
var customer = new CustomerService();
Customer customer = service.Create(options);
If you have an existing Customer, you can attach the PaymentMethod to that object instead.
curl https://api.stripe.com/v1/payment_methods/{{PAYMENT_METHOD_ID}}/attach \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d customer="{{CUSTOMER_ID}}"
payment_method = Stripe::PaymentMethod.attach(
intent.payment_method,
{
customer: '{{CUSTOMER_ID}}',
}
)
payment_method = stripe.PaymentMethod.attach(
intent.payment_method,
customer='{{CUSTOMER_ID}}'
)
$payment_method = \Stripe\PaymentMethod::retrieve($intent->payment_method);
$payment_method->attach(['customer' => '{{CUSTOMER_ID}}']);
PaymentMethod paymentMethod = PaymentMethod.retrieve(intent.getPaymentMethod());
Map<String, Object> params = new HashMap<String, Object>();
params.put("customer", "{{CUSTOMER_ID}}");
paymentMethod.attach(params);
const paymentMethod = await stripe.paymentMethods.attach(intent.payment_method, {
customer: '{{CUSTOMER_ID}}',
});
params := &stripe.PaymentMethodAttachParams{
Customer: stripe.String("{{CUSTOMER_ID}}"),
}
p, err := paymentmethod.Attach(stripe.String(intent.PaymentMethod.ID), params)
var options = new PaymentMethodAttachOptions
{
CustomerId = "{{CUSTOMER_ID}}"
};
var service = new PaymentMethodService();
var paymentMethod = service.Attach(intent.PaymentMethodId, options);
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 card details again.
Next, test your integration to make sure you’re correctly handling cards that require additional authentication.
Saving card details without a payment
Use the Setup Intents API when you want to collect card details up front, but you’re not sure when or how much you intend to charge the customer in the future. Common scenarios include:
- Saving payment methods to a wallet to streamline future checkout experiences
- Reserving the ability to collect surcharges after fulfilling a service
- Starting a free trial for a variable-amount subscription
Saving card details without a payment consists of the following steps:
Step 1: Create a SetupIntent on the server
A SetupIntent is an object that represents your intent to set up a payment method for future payments. This object tracks any steps necessary to set up the payment method provided by your customer for future payments. For cards, this may include authenticating the customer or checking the validity of the card with the cardholder’s bank. The following example shows how to create a SetupIntent on your server:
curl https://api.stripe.com/v1/setup_intents \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc:
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
Stripe::SetupIntent.create
setup_intent = stripe.SetupIntent.create()
$setup_intent = \Stripe\SetupIntent::create([])
Map<String, Object> params = new HashMap<>();
SetupIntent setupIntent = SetupIntent.create(params);
(async () => {
const setupIntent = await stripe.setupIntents.create({})
})();
params := &stripe.SetupIntentParams{}
intent, err := setupIntent.New(params)
var options = new SetupIntentCreateOptions{};
var service = new SetupIntentService();
SetupIntent setupIntent = service.Create(options);
The usage parameter sets up the payment method to be optimized for future payment flows. Setting usage
to off_session
will properly authenticate the card to be used for off-session payments. While this may create initial friction in the setup flow by requiring authentication when saving the card with SetupIntent, it will reduce the need to authenticate later off-session payments.
To select a value for usage
, consider how you want to use this payment method in the future.
How you intend to use the card | usage enum value to use |
---|---|
On-sessionA payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method. payments only | on_session |
Off-sessionA payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information. payments only | off_session (default) |
Both on and off-session payments | off_session (default) |
usage
is an optimization. A card set up for on-session payments can still be used to make off-session payments, but there’s a higher likelihood that the bank will reject the off-session payment and require authentication from the cardholder.
If not specified, usage
will default to off_session
.
The following example shows how to create a SetupIntent on your server while also specifying usage
:
curl https://api.stripe.com/v1/setup_intents \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d usage=on_session
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
Stripe::SetupIntent.create(
usage: 'on_session' # The default usage is off_session
)
setup_intent = stripe.SetupIntent.create(
usage='on_session', # The default usage is off_session
)
$setup_intent = \Stripe\SetupIntent::create([
'usage' => 'on_session', // The default usage is off_session
])
Map<String, Object> params = new HashMap<>();
// The default usage is off_session
params.put("usage", "on_session");
SetupIntent setupIntent = SetupIntent.create(params);
(async () => {
const setupIntent = await stripe.setupIntents.create({
usage: 'on_session', // The default usage is off_session
})
})();
params := &stripe.SetupIntentParams{
// The default usage is off_session
Usage: stripe.String(string(stripe.PaymentIntentSetupFutureUsageOnSession)),
}
intent, err := setupIntent.New(params)
// The default usage is off_session
var options = new SetupIntentCreateOptions
{
Usage = "on_session",
};
var service = new SetupIntentService();
SetupIntent setupIntent = service.Create(options);
Step 2: Pass the SetupIntent’s client secret to the client
The SetupIntent object contains a client secret, a unique key that you need to pass to Stripe.js on the client side in order to collect card details. If your application uses server-side rendering, use your template framework to embed the client secret in the page using a data attribute or a hidden HTML element.
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="<%= @intent.client_secret %>">Save Card</button>
get '/card-wallet' do
@intent = # ... Fetch or create the SetupIntent
erb :card_wallet
end
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
Save Card
</button>
@app.route('/card-wallet')
def card_wallet():
intent = # ... Fetch or create the SetupIntent
return render_template('card_wallet.html', client_secret=intent.client_secret)
<?php
$intent = # ... Fetch or create the SetupIntent;
?>
...
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="<?= $intent->client_secret ?>">
Save Card
</button>
...
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
Save Card
</button>
import java.util.HashMap;
import java.util.Map;
import com.stripe.model.SetupIntent;
import spark.ModelAndView;
import static spark.Spark.get;
public class StripeJavaQuickStart {
public static void main(String[] args) {
get("/card-wallet", (request, response) -> {
SetupIntent intent = // ... Fetch or create the SetupIntent
Map<String, String> map = new HashMap();
map.put("client_secret", intent.getClientSecret());
return new ModelAndView(map, "card_wallet.hbs");
}, new HandlebarsTemplateEngine());
}
}
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
Save Card
</button>
const express = require('express');
const expressHandlebars = require('express-handlebars');
const app = express();
app.engine('.hbs', expressHandlebars({ extname: '.hbs' }));
app.set('view engine', '.hbs');
app.set('views', './views');
app.get('/card-wallet', async (req, res) => {
const intent = // ... Fetch or create the SetupIntent
res.render('card_wallet', { client_secret: intent.client_secret });
});
app.listen(3000, () => {
console.log('Running on port 3000')
});
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ .ClientSecret }}">
Save Card
</button>
package main
import (
"html/template"
"net/http"
stripe "github.com/stripe/stripe-go"
)
type WalletData struct {
ClientSecret string
}
func main() {
cardWalletTmpl := template.Must(template.ParseFiles("views/card_wallet.html"))
http.HandleFunc("/card-wallet", func(w http.ResponseWriter, r *http.Request) {
intent := // ... Fetch or create the SetupIntent
data := WalletData{
ClientSecret: intent.ClientSecret,
}
cardWalletTmpl.Execute(w, data)
})
http.ListenAndServe(":3000", nil)
}
<input id="cardholder-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret='@ViewData["ClientSecret"]'>
Save Card
</button>
using System;
using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace StripeExampleApi.Controllers
{
[Route("/[controller]")]
public class CardWalletController : Controller
{
public IActionResult Index()
{
var intent = // ... Fetch or create the SetupIntent
ViewData["ClientSecret"] = intent.ClientSecret;
return View();
}
}
}
Create a SetupIntent any time you need to collect card details from a customer in your application. In most cases, create a SetupIntent immediately before you need to collect card details from a customer. After you’ve created a SetupIntent on your server, associate its ID with the current session’s customer in your application’s data model. That way you can retrieve it after you have successfully collected a card.
The client secret can be used to validate and authenticate card details via the credit card networks. It should not be logged, embedded in URLs, or exposed to anyone other than the customer. Make sure that you have TLS enabled on any page that includes the client secret.
Step 3: Collect payment method details on the client
The Setup Intents API is fully integrated with Stripe.js, using Elements to securely collect payment information on the client side and submitting it to Stripe to validate and authenticate for future usage. To get started with Elements, include the following script on your pages. This script must always load directly from js.stripe.com in order to remain PCI compliantAny party involved in processing, transmitting, or storing credit card data must comply with the rules specified in the the Payment Card Industry (PCI) Data Security Standards. PCI compliance is a shared responsibility and applies to both Stripe and your business.—you can’t include it in a bundle or host a copy of it yourself.
<script src="https://js.stripe.com/v3/"></script>
To best leverage Stripe’s advanced fraud functionality, include this script on every page on your site, not just the checkout page. Including the script on every page allows Stripe to detect anomalous behavior that may be indicative of fraud as users browse your website.
Next, create an instance of the Stripe object, providing your publishable API key as the first parameter. Afterwards, create an instance of the Elements object and use it to mount a Card element in the relevant placeholder in the page.
var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
var elements = stripe.elements();
var cardElement = elements.create('card');
cardElement.mount('#card-element');
const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
Step 4: Submit the card details to Stripe from the client
To complete the setup, retrieve the client secret made available in the second step. After obtaining the client secret from the data attribute, use stripe.handleCardSetup to complete the setup:
var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');
var clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', function(ev) {
stripe.handleCardSetup(
clientSecret, cardElement, {
payment_method_data: {
billing_details: {name: cardholderName.value}
}
}
).then(function(result) {
if (result.error) {
// Display error.message in your UI.
} else {
// The setup has succeeded. Display a success message.
}
});
});
const cardholderName = document.getElementById('cardholder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (ev) => {
const {setupIntent, error} = await stripe.handleCardSetup(
clientSecret, cardElement, {
payment_method_data: {
billing_details: {name: cardholderName.value}
}
}
);
if (error) {
// Display error.message in your UI.
} else {
// The setup has succeeded. Display a success message.
}
});
The SetupIntent verifies that the card information your customer is using is valid on the network.
If the customer must perform additional steps to complete the setup, such as authentication, Stripe.js walks them through that process. When the setup completes successfully, the value of the returned SetupIntent’s status
property is succeeded
. If the setup is not successful, you can inspect the returned error
to determine the cause.
Step 5: Attach the PaymentMethod to a Customer after success
Once the SetupIntent has succeeded, associate the card details with a Customer object.
When creating a new Customer, pass a PaymentMethod ID to immediately add a payment method.
curl https://api.stripe.com/v1/customers \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d payment_method="{{PAYMENT_METHOD_ID}}"
# This creates a new Customer and attaches the PaymentMethod in one API call.
customer = Stripe::Customer.create(payment_method: intent.payment_method)
# This creates a new Customer and attaches the PaymentMethod in one API call.
stripe.Customer.create(
payment_method=intent.payment_method
)
// This creates a new Customer and attaches the PaymentMethod in one API call.
\Stripe\Customer::create([
'payment_method' => $intent->payment_method,
]);
// This creates a new Customer and attaches the PaymentMethod in one API call.
Map<String, Object> customerParams = new HashMap<String, Object>();
customerParams.put("payment_method", intent.getPaymentMethod());
Customer.create(customerParams);
// This creates a new Customer and attaches the PaymentMethod in one API call.
const customer = await stripe.customers.create({
payment_method: intent.payment_method,
});
// This creates a new Customer and attaches the PaymentMethod in one API call.
customerParams := &stripe.CustomerParams{
PaymentMethod: intent.PaymentMethod.ID,
}
c, err := customer.New(customerParams)
var options = new CustomerCreateOptions {
PaymentMethodId = intent.PaymentMethodId
};
var customer = new CustomerService();
Customer customer = service.Create(options);
If you have an existing Customer, you can attach the PaymentMethod to that object instead.
curl https://api.stripe.com/v1/payment_methods/{{PAYMENT_METHOD_ID}}/attach \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d customer="{{CUSTOMER_ID}}"
payment_method = Stripe::PaymentMethod.attach(
intent.payment_method,
{
customer: '{{CUSTOMER_ID}}',
}
)
payment_method = stripe.PaymentMethod.attach(
intent.payment_method,
customer='{{CUSTOMER_ID}}'
)
$payment_method = \Stripe\PaymentMethod::retrieve($intent->payment_method);
$payment_method->attach(['customer' => '{{CUSTOMER_ID}}']);
PaymentMethod paymentMethod = PaymentMethod.retrieve(intent.getPaymentMethod());
Map<String, Object> params = new HashMap<String, Object>();
params.put("customer", "{{CUSTOMER_ID}}");
paymentMethod.attach(params);
const paymentMethod = await stripe.paymentMethods.attach(intent.payment_method, {
customer: '{{CUSTOMER_ID}}',
});
params := &stripe.PaymentMethodAttachParams{
Customer: stripe.String("{{CUSTOMER_ID}}"),
}
p, err := paymentmethod.Attach(stripe.String(intent.PaymentMethod.ID), params)
var options = new PaymentMethodAttachOptions
{
CustomerId = "{{CUSTOMER_ID}}"
};
var service = new PaymentMethodService();
var paymentMethod = service.Attach(intent.PaymentMethodId, options);
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 card details again.
Test the integration
It’s important to thoroughly test your integration to make sure you’re correctly handling cards that require additional authentication and cards that don’t. Use these card numbers in test mode with any expiration date in the future and any three digit CVC code to validate your integration when authentication is required and when it’s not required.
Number | Authentication | Description |
---|---|---|
4000002500003155 | Required on setup or first transaction | This test card requires authentication for one-time payments. However, if you set up this card using the Setup Intents API and use the saved card for subsequent payments, no further authentication is needed. |
4000002760003184 | Required | This test card requires authentication on all transactions. |
4000008260003178 | Required |
This test card requires authentication, but payments will be declined with an insufficient_funds failure code after successful authentication.
|
4000000000003055 | Supported | This test card supports authentication via 3D Secure 2, but does not require it. Payments using this card do not require additional authentication in test mode unless your test mode Radar rules request authentication. |
Use these cards in your application or the payments demo to see the different behavior.
Next steps
Now you’re able to save card details while optimizing for future payments. To learn more, continue reading: