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:

    1. Create a PaymentIntent on the server
    2. Pass the PaymentIntent’s client secret to the client
    3. Collect payment method details on the client
    4. Submit the payment to Stripe from the client
    5. Attach the PaymentMethod to a Customer after success

    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-session payments only on_session
    Off-session 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 compliant—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 fulfillment 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:

    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:

    1. Create a SetupIntent on the server
    2. Pass the SetupIntent’s client secret to the client
    3. Collect payment method details on the client
    4. Submit the card details to Stripe from the client
    5. Attach the PaymentMethod to a Customer after success

    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-session payments only on_session
    Off-session 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 compliant—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:

    On this page