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, and additionally set use_stripe_sdk to true for mobile clients.

    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 pass to Stripe.js on the client side 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 the data model of your application, retrieving it again as necessary.

    You can use the client secret to complete the payment process with the amount specified on the PaymentIntent. Do not log it, embed it in URLs, or expose it to anyone other than the customer. Make sure that you have TLS enabled on any page that includes the client secret.

    The PaymentIntent object contains a client secret, a unique key that you pass to your app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint that you set up in Step 1 to create a PaymentIntent.

    MyAPIClient.createPaymentIntent(amount: 100, currency: "usd", setupFutureUsage: "off_session") { result in
      switch (result) {
        case .success(let clientSecret):
          // Hold onto clientSecret for Step 4
        case .failure(let error):
          // Handle the error
      }
    }
    [MyAPIClient createPaymentIntentWithAmount:100
                      currency:@"usd"
                      setupFutureUsage: @"off_session"
                      completion:^(NSString *clientSecret, NSError *error) {
                        if (error != nil) {
                          // Handle the error
                        } else {
                          // Hold onto clientSecret for Step 4
                        }
                      }];

    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 the data model of your application, retrieving it again as necessary.

    You can use the client secret to complete the payment process with the amount specified on the PaymentIntent. Do not log it, embed it in URLs, or expose it to anyone other than the customer. Make sure that you have TLS enabled on any page that includes the client secret.

    The PaymentIntent object contains a client secret, a unique key that you pass to your app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint that you set up in Step 1 to create a PaymentIntent.

    public class PaymentActivity extends Activity {
      private Stripe mStripe;
    
      @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          PaymentConfiguration.init("pk_test_TYooMQauvdEDq54NiTphI7jx");
          mStripe = new Stripe(this,
              PaymentConfiguration.getInstance().getPublishableKey());
    
          // Create the PaymentIntent on your backend
          myBackendApiClient.createPaymentIntent(100, "usd", "off_session",
              new ApiResultCallback<String>() {
                  @Override
                  public void onSuccess(@NonNull String clientSecret) {
                      // Hold onto the clientSecret for Step 4
                  }
    
                  @Override
                  public void onError(@NonNull Exception e) {
                  }
              });
    
      }
    }
    class PaymentActivity : Activity() {
      private lateinit var stripe: Stripe
    
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          PaymentConfiguration.init("pk_test_TYooMQauvdEDq54NiTphI7jx")
          stripe = Stripe(this,
              PaymentConfiguration.getInstance().publishableKey)
    
          // create the PaymentIntent on your backend
          myBackendApiClient.createPaymentIntent(
              amount: 100, currency: "usd", setupFutureUsage: "off_session",
              object: ApiResultCallback<String> {
                  override fun onSuccess(clientSecret: String) {
                      // Hold onto the clientSecret for Step 4
                  }
    
                  override fun onError(e: Exception) {
                  }
              })
      }
    }

    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 the data model of your application, retrieving it again as necessary.

    You can use the client secret to complete the payment process with the amount specified on the PaymentIntent. Do not log it, embed it in URLs, or expose it to anyone other than the customer.

    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');

    If you already have a PaymentMethod or Source that you would like to associate with the PaymentIntent, such as a STPPaymentMethod from an STPPaymentContext integration, skip this step.

    In your application, collect card details from the customer and pass the collected information into new STPPaymentMethodCardParams and STPPaymentMethodBillingDetails instances to create a PaymentMethod.

    let cardParams = STPPaymentMethodCardParams()
    let billingDetails = STPPaymentMethodBillingDetails()
    // Fill in card, billing details
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
    STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams) { paymentMethod, error in
      // Hold onto the paymentMethod for Step 4
    }
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new];
    // Fill in card, billing details
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:billingDetails metadata:nil];
    [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:^(STPPaymentMethod *paymentMethod, NSError *error) {
      // Hold onto the paymentMethod for Step 4
    }];

    Apple Pay is supported. Instead of collecting card details:

    If you already have a PaymentMethod that you would like to associate with the PaymentIntent, you may skip this step.

    In your application, collect card details from the customer. There are a few ways to do this:

    • You can use CardInputWidget or the CardMultilineWidget, calling the getCard method to retrieve a Card object.
    • You can collect payment information through your own form and pass the collected information into a new Card instance.
    • You can use the token returned from Google Pay.

    Once you have the Card object, convert it to a PaymentMethodCreateParams.Card using Card#toPaymentMethodParamsCard and pass it to PaymentMethodCreateParams.create to create a new PaymentMethodCreateParams object. For more information on creating a PaymentMethod, see the Payment Methods API guide for Android.

    The following code demonstrates how to create a PaymentMethod from a Card

    PaymentMethodCreateParams.Card paymentMethodParamsCard =
        mCardInputWidget.getCard().toPaymentMethodParamsCard();
    PaymentMethodCreateParams cardPaymentMethodCreateParams =
        PaymentMethodCreateParams.create(paymentMethodParamsCard, null);
    stripe.createPaymentMethod(cardPaymentMethodCreateParams,
        new ApiResultCallback<PaymentMethod>() {
            @Override
            public void onSuccess(@NonNull PaymentMethod paymentMethod) {
                // Hold onto the paymentMethod for Step 4
            }
    
            @Override
            public void onError(@NonNull Exception e) {
            }
        });
    val paymentMethodParamsCard = mCardInputWidget.getCard().toPaymentMethodParamsCard()
    val cardPaymentMethodCreateParams =
        PaymentMethodCreateParams.create(paymentMethodParamsCard, null)
    stripe.createPaymentMethod(cardPaymentMethodCreateParams,
        object: ApiResultCallback<PaymentMethod> {
            override fun onSuccess(paymentMethod: PaymentMethod) {
                // Hold onto the paymentMethod for Step 4 to onSuccess
            }
    
            override fun onError(e: Exception) {
            }
        })

    Step 4: Submit the payment to Stripe from the client

    Automatic Confirmation

    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, create an STPPaymentIntentParams instance from the STPPaymentMethod from Step 3 and the client secret from Step 2 to STPPaymentHandler. There are 2 ways to do this:

    Pass the STPPaymentIntentParams object to the confirmPayment method on a STPPaymentHandler sharedManager. If the customer must perform additional steps to complete the setup, such as authentication, STPPaymentHandler presents view controllers on the STPAuthenticationContext passed in and walks them through that process.

    The following sample code assumes that MyCheckoutViewController should present any additional view controllers and implements STPAuthenticationContext.

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
    paymentIntentParams.paymentMethodId = paymentMethodId
    let paymentManager = STPPaymentHandler.shared()
    paymentManager.confirmPayment(paymentIntentParams, authenticationContext: self, completion { (status, paymentIntent, error) in
      switch (status) {
        case .succeeded:
          // Payment succeeded
        case .canceled:
          // Handle cancel
        case .failed:
          // Handle error
      }
    })
    STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:client_secret];
    paymentIntentParams.paymentMethodId = paymentMethodId;
    [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams
                            withAuthenticationContext:self
                                           completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * error) {
      switch (handlerStatus) {
        case STPPaymentHandlerActionStatusSucceeded:
          // Payment succeeded
        case STPPaymentHandlerActionStatusCanceled:
          // Handle cancel
        case STPPaymentHandlerActionStatusFailed:
          // Handle error
      }
    }];

    Fulfill the customer’s order asynchronously

    You can use the PaymentIntent returned by STPPaymentHandler to provide immediate feedback to your customers when the payment completes in the app. However, your integration should not attempt to handle order fulfillment on the client side because it is possible for customers to leave the app 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, create a PaymentIntentParams object from the desired PaymentMethod and pass it with the current Activity to Stripe#confirmPayment(). Some payment methods require additional authentication steps in order to complete payment. The SDK manages the payment confirmation and authentication flow, which may involve presenting additional screens required for authentication. For more information on 3D Secure authentication and customizing the authentication experience, see Supporting 3D Secure Authentication on Android.

    The result of the flow returns to your calling Activity via Activity#onActivityResult(). Handle the result by calling Stripe#onPaymentResult() within Activity#onActivityResult(). The PaymentIntentResult returned in ApiResultCallback#onSuccess() has two fields:

    • paymentIntent - a PaymentIntent object that has been retrieved after confirmation/authentication
    • status - a PaymentIntentResult.Status value that indicates the outcome of payment authentication
      • SUCCEEDED - confirmation or payment authentication succeeded
      • FAILED - confirmation or payment authentication failed
      • CANCELED - the customer canceled required payment authentication
    private void confirmPayment(@NonNull String paymentMethodId) {
        final ConfirmPaymentIntentParams confirmPaymentIntentParams =
            ConfirmPaymentIntentParams.createWithPaymentMethodId(
                paymentMethodId, clientSecret);
    
        // Optional: customize the Payment Authentication experience
        final StripeUiCustomization uiCustomization = new StripeUiCustomization();
        PaymentAuthConfig.init(new PaymentAuthConfig.Builder()
                .set3ds2Config(new PaymentAuthConfig.Stripe3ds2Config.Builder()
                        // set a 5 minute timeout for challenge flow
                        .setTimeout(5)
                        // customize the UI of the challenge flow
                        .setUiCustomization(uiCustomization)
                        .build())
                .build());
    
        mStripe.confirmPayment(this, confirmPaymentIntentParams);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mStripe.onPaymentResult(
                requestCode, resultCode, data,
                new ApiResultCallback<PaymentIntentResult>() {
                    @Override
                    public void onSuccess(@NonNull PaymentIntentResult result) {
                        // If confirmation and authentication succeeded,
                        // the PaymentIntent will have user actions resolved;
                        // otherwise, handle the failure as appropriate
                        // (e.g. the customer may need to choose a new payment
                        // method)
                        final PaymentIntent paymentIntent = result.getIntent();
                        final PaymentIntent.Status status =
                            paymentIntent.getStatus();
                        if (status == PaymentIntent.Status.Succeeded) {
                            // show success UI
                        } else if (paymentIntent.requiresConfirmation()) {
                            // handle confirmation
                        }
                    }
    
                    @Override
                    public void onError(@NonNull Exception e) {
                        // handle error
                    }
                });
    }
    private fun confirmPayment(paymentMethodId: String, clientSecret: String) {
        val confirmPaymentIntentParams =
            ConfirmPaymentIntentParams.createWithPaymentMethodId(
                paymentMethodId, clientSecret)
    
        // Optional: customize the Payment Authentication experience
        val uiCustomization = StripeUiCustomization()
        PaymentAuthConfig.init(PaymentAuthConfig.Builder()
            .set3ds2Config(PaymentAuthConfig.Stripe3ds2Config.Builder()
                // set a 5 minute timeout for challenge flow
                .setTimeout(5)
                // customize the UI of the challenge flow
                .setUiCustomization(uiCustomization)
                .build())
            .build())
    
        stripe.confirmPayment(this, confirmPaymentIntentParams)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        super.onActivityResult(requestCode, resultCode, data)
        stripe.onPaymentResult(
            requestCode, resultCode, data,
            object : ApiResultCallback<PaymentIntentResult> {
                override fun onSuccess(result: PaymentIntentResult) {
                    // If confirmation and authentication succeeded,
                    // the PaymentIntent will have user actions resolved;
                    // otherwise, handle the failure as appropriate
                    // (e.g. the customer may need to choose a new payment
                    // method)
                    val paymentIntent = result.intent
                    val status = paymentIntent.status
                    if (status == StripeIntent.Status.Succeeded) {
                        // show success UI
                    } else if (paymentIntent.requiresConfirmation()) {
                        // handle confirmation
                    }
                }
    
                override fun onError(e: Exception) {
                    // handle error
                }
            })
    }

    Fulfill the customer’s order asynchronously

    You can use the PaymentIntent returned in PaymentIntentResult to provide immediate feedback to your customers when the payment completes in the app. However, your integration should not attempt to handle order fulfillment on the client side because it is possible for customers to leave the app 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:

    Manual Confirmation

    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.

    To complete the payment, confirm the PaymentIntent created in Step 1 on your backend, using the client secret from Step 2 and the identifier of the PaymentMethod from Step 3.

    Next, pass the confirmed PaymentIntent to STPPaymentHandler handleNextActionForPayment. If the customer must perform additional steps to complete the setup, such as authentication, STPPaymentHandler presents view controllers on the STPAuthenticationContext passed in and walks them through that process.

    The following sample code assumes that MyCheckoutViewController should present any additional view controllers and implements STPAuthenticationContext. It also assumes confirmPaymentIntent asks your backend to confirm the Payment Intent, refetches the PaymentIntent, and returns it in the completion block. Don’t return the PaymentIntent in your backend response; always refetch the PaymentIntent on the client instead.

    MyAPIClient.confirmPaymentIntent(clientSecret: clientSecret, paymentMethodId: paymentMethodId) { result in
      guard case .success(let paymentIntent) = result else {
        // Handle error
        return
      }
      STPPaymentHandler.shared().handleNextAction(forPayment: paymentIntent, authenticationContext: self) { (status, completedPaymentIntent, error) in
        switch (status) {
          case .succeeded:
            // Confirm the PaymentIntent again on the backend
            MyAPIClient.confirmPaymentIntent(clientSecret: completedPaymentIntent.clientSecret) { result in
              guard case .success(let paymentIntent) = result else {
                // Handle error
                return
              }
              if paymentIntent.status == .succeeded {
                // Success
              } else {
                // Handle error by inspecting the status
              }
            }
          case .canceled:
            // Handle cancel
          case .failed:
            // Handle error
        }
      }
    }
    [MyAPIClient confirmPaymentIntentWithClientSecret:clientSecret paymentMethodId: paymentMethodId completion:^(STPPaymentIntent * paymentIntent, NSError * error) {
      if (error != nil) {
        // Handle the error
        return;
      }
    
      [[STPPaymentHandler sharedHandler] handleNextActionForPayment:paymentIntent withAuthenticationContext:self.delegate completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * handledIntent, NSError * handlerError) {
        switch (status) {
          case STPPaymentHandlerActionStatusSucceeded:
            [MyAPIClient confirmPaymentIntentWithClientSecret:clientSecret completion:^(STPPaymentIntent *paymentIntent, NSError *error) {
              if (error != nil) {
                // Handle the error
                return;
              }
              if (paymentIntent.status == STPPaymentIntentStatusSucceeded) {
                  // Success
              } else {
                  // Handle error by inspecting the status
              }
            }];
          case STPPaymentHandlerActionStatusCanceled:
            // Handle cancel
          case STPPaymentHandlerActionStatusFailed:
            // Handle error
        }
      }];
    }];

    To complete the payment, confirm the PaymentIntent created in Step 1 on your backend using the client secret from Step 2 and the identifier of the PaymentMethod from Step 3.

    Some payment methods may require additional authentication steps in order to complete a payment. To determine whether further action is required from your customer, check the status of the PaymentIntent object returned from confirmation.

    If the PaymentIntent’s status property is requires_action, further action is required from your customer in order to complete the payment. Pass the current Activity and PaymentIntent object to Stripe#authenticatePayment(). The SDK manages the payment authentication flow, which may involve presenting additional screens required for authentication. For more information on 3D Secure authentication and customizing the authentication experience, see Supporting 3D Secure Authentication on Android.

    The result of payment authentication returns to your calling Activity via Activity#onActivityResult(). Handle the result by calling Stripe#onPaymentResult() within Activity#onActivityResult(). The PaymentIntentResult returned in ApiResultCallback#onSuccess() has two fields:

    • paymentIntent - a PaymentIntent object that has been retrieved after confirmation/authentication
    • status - a PaymentIntentResult.Status value that indicates the outcome of payment authentication
      • SUCCEEDED - payment authentication succeeded
      • FAILED - payment authentication failed
      • CANCELED - the customer canceled required payment authentication
    private void confirmPaymentIntent() {
        myBackendApiClient.confirmPaymentIntent(params,
            new ApiResultCallback<String>() {
                @Override
                public void onSuccess(@NonNull String clientSecret) {
                    retrievePaymentIntent(clientSecret);
                }
    
                @Override
                public void onError(@NonNull Exception e) {
                    // handle error
                }
            });
    }
    
    private void retrievePaymentIntent(@NonNull String clientSecret) {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                processPaymentIntent(mStripe.retrievePaymentIntentSynchronous(clientSecret));
            }
        });
    }
    
    private void processPaymentIntent(@NonNull PaymentIntent paymentIntent) {
        if (PaymentIntent.Status.RequiresAction == paymentIntent.getStatus()) {
            // Optional: customize the Payment Authentication experience
            final StripeUiCustomization uiCustomization = new StripeUiCustomization();
            PaymentAuthConfig.init(new PaymentAuthConfig.Builder()
                    .set3ds2Config(new PaymentAuthConfig.Stripe3ds2Config.Builder()
                            // set a 5 minute timeout for challenge flow
                            .setTimeout(5)
                            // customize the UI of the challenge flow
                            .setUiCustomization(uiCustomization)
                            .build())
                    .build());
            mStripe.authenticatePayment(this, paymentIntent.getClientSecret());
        } else if (PaymentIntent.Status.Succeeded == paymentIntent.getStatus()) {
            // payment captured succeeded
        }
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mStripe.onPaymentResult(
            requestCode, resultCode, data,
            new ApiResultCallback<PaymentIntentResult>() {
                @Override
                public void onSuccess(@NonNull PaymentIntentResult result) {
                    // If authentication succeeded, the PaymentIntent will have
                    // user actions resolved; otherwise, handle the failure as
                    // appropriate (e.g. the customer may need to choose a new
                    // payment method)
    
                    final PaymentIntent.Status status = result.getIntent().getStatus();
                    if (PaymentIntent.Status.RequiresPaymentMethod == status) {
                        // handle payment authentication failed
                    } else if (PaymentIntent.Status.RequiresConfirmation == status) {
                        // handle confirming the PaymentIntent again on the server
                    }
                }
    
                @Override
                public void onError(@NonNull Exception e) {
                    // handle error
                }
            });
    }
    private fun confirmPaymentIntent() {
        myBackendApiClient.confirmPaymentIntent(params,
            object: ApiResultCallback<String> {
                override fun onSuccess(clientSecret: String) {
                    retrievePaymentIntent(clientSecret)
                }
    
                override fun onError(e: Exception) {
                    // handle error
                }
          })
    }
    
    private fun retrievePaymentIntent(clientSecret: String) {
        AsyncTask.execute {
            processPaymentIntent(mStripe.retrievePaymentIntentSynchronous(clientSecret))
        }
    }
    
    private fun processPaymentIntent(paymentIntent: PaymentIntent) {
        if (PaymentIntent.Status.RequiresAction == paymentIntent.status) {
            // Optional: customize the Payment Authentication experience
            val uiCustomization = StripeUiCustomization()
            PaymentAuthConfig.init(PaymentAuthConfig.Builder()
                .set3ds2Config(PaymentAuthConfig.Stripe3ds2Config.Builder()
                    // set a 5 minute timeout for challenge flow
                    .setTimeout(5)
                    // customize the UI of the challenge flow
                    .setUiCustomization(uiCustomization)
                    .build())
                .build())
    
            stripe.authenticatePayment(this, paymentIntent.clientSecret)
        } else if (PaymentIntent.Status.Succeeded == paymentIntent.status) {
            // payment captured succeeded
        }
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        super.onActivityResult(requestCode, resultCode, data)
        stripe.onPaymentResult(
            requestCode, resultCode, data,
            object : ApiResultCallback<PaymentIntentResult> {
                override fun onSuccess(result: PaymentIntentResult) {
                    // If authentication succeeded, the PaymentIntent will have
                    // user actions resolved; otherwise, handle the failure as
                    // appropriate (e.g. the customer may need to choose a new
                    // payment method)
    
                    val status = result.intent.status
                    if (StripeIntent.Status.RequiresPaymentMethod == status) {
                        // handle payment authentication failed
                    } else if (StripeIntent.Status.RequiresConfirmation == status) {
                        // handle confirming the PaymentIntent again on the server
                    }
                }
    
                override fun onError(e: Exception) {
                    // handle error
                }
            })
    }

    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(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'
    
    setup_intent = 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'
    
    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
    )
    
    $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.SetupIntentUsageOnSession)),
    }
    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 immediately before collecting and saving card details without a payment in your application. If needed, you can also create a SetupIntent after collecting card details. After creating 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.

    The SetupIntent object contains a client secret, a unique key that you pass to your app to collect card details.

    MyAPIClient.createSetupIntent(amount: 100, currency: "usd") { result in
      switch (result) {
        case .success(let clientSecret):
          // Hold onto clientSecret for step 4
        case .failure(let error):
          // Handle the error
      }
    }
    [MyAPIClient createSetupIntentWithAmount:100
                      currency:@"usd"
                      completion:^(NSString *clientSecret, NSError *error) {
                        if (error != nil) {
                          // Handle the error
                        } else {
                          // Hold onto clientSecret for step 4
                        }
                      }];

    Create a SetupIntent immediately before collecting and saving card details without a payment in your application. If needed, you can also create a SetupIntent after collecting card details. After creating 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.

    The SetupIntent object contains a client secret, a unique key that you pass to your app to collect card details.

    public class PaymentActivity extends Activity {
        private Stripe mStripe;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            PaymentConfiguration.init("pk_test_TYooMQauvdEDq54NiTphI7jx");
            mStripe = new Stripe(this,
                PaymentConfiguration.getInstance().getPublishableKey());
    
            // now retrieve the SetupIntent that was created on your backend and hold onto the client secret for step 4
        }
    }
    class PaymentActivity : Activity() {
        private lateinit var stripe: Stripe
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            PaymentConfiguration.init("pk_test_TYooMQauvdEDq54NiTphI7jx")
            stripe = Stripe(this,
                PaymentConfiguration.getInstance().publishableKey)
    
            // now retrieve the SetupIntent that was created on your backend and hold onto the client secret for step 4
        }
    }

    Create a SetupIntent immediately before collecting and saving card details without a payment in your application. If needed, you can also create a SetupIntent after collecting card details. After creating 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.

    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');

    If you already have a PaymentMethod that you would like to associate with the SetupIntent, such as an STPPaymentMethod from an STPPaymentContext integration, skip this step.

    In your application, collect card details from the customer and pass the collected information into new STPPaymentMethodCardParams and STPPaymentMethodBillingDetails instances to create a PaymentMethod.

    let cardParams = STPPaymentMethodCardParams()
    let billingDetails = STPPaymentMethodBillingDetails()
    // Fill in card, billing details
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
    STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams) { paymentMethod, error in
      // Hold onto paymentMethod.stripeId for step 4
    }
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new];
    // Fill in card, billing details
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:billingDetails metadata:nil];
    [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:^(STPPaymentMethod *paymentMethod, NSError *error) {
      // Hold onto paymentMethod.stripeId for step 4
    }];

    Apple Pay is supported. Instead of collecting card details:

    In your application, collect card details from the customer. There are a few ways to do this:

    • Use CardInputWidget or CardMultilineWidget, calling the getCard method to retrieve a Card object
    • Collect payment information through your own form and pass the collected information into a new Card instance
    • Use the token returned from Google Pay

    Once you have the Card object, convert it to a PaymentMethodCreateParams.Card using Card#toPaymentMethodParamsCard and pass it to PaymentMethodCreateParams.create to create a new PaymentMethodCreateParams object. If you already have a PaymentMethod that you would like to associate with the SetupIntent, skip this step. For more information on creating a PaymentMethod, see the Payment Methods API guide for Android.

    Create a PaymentMethod from a Card:

    PaymentMethodCreateParams.Card paymentMethodParamsCard =
        mCardInputWidget.getCard().toPaymentMethodParamsCard();
    PaymentMethodCreateParams cardPaymentMethodCreateParams =
        PaymentMethodCreateParams.create(paymentMethodParamsCard, billingDetails);
    stripe.createPaymentMethod(paymentMethodCreateParams,
        new ApiResultCallback<PaymentMethod>() {
            @Override
            public void onSuccess(@NonNull PaymentMethod result) {
                // Hold onto the PaymentMethod for step 4
            }
    
            @Override
            public void onError(@NonNull Exception e) {
            }
        });
    val paymentMethodParamsCard =
        mCardInputWidget.getCard().toPaymentMethodParamsCard()
    val cardPaymentMethodCreateParams = stripe.
        PaymentMethodCreateParams.create(paymentMethodParamsCard, billingDetails)
    stripe.createPaymentMethod(cardPaymentMethodCreateParams,
        object: ApiResultCallback<PaymentMethod> {
            override fun onSuccess(result: PaymentMethod) {
                // Hold onto the PaymentMethod for step 4
            }
    
            override fun onError(e: Exception) {
            }
        })

    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.

    To complete the setup, create an STPSetupIntentParams instance from the STPPaymentMethod from step 3 and the client secret from step 2 to STPPaymentHandler. There are two ways to do this:

    Pass the STPSetupIntentParams object to the confirmSetupIntent method on a STPPaymentHandler sharedManager. If the customer must perform additional steps to complete the setup, such as authentication, STPPaymentHandler presents view controllers on the STPAuthenticationContext passed in and walks them through that process.

    The following sample code assumes that MyCheckoutViewController presents any additional view controllers and implements STPAuthenticationContext.

    let setupIntentParams = STPSetupIntentParams(clientSecret: clientSecret)
    setupIntentParams.paymentMethodId = paymentMethodId
    let paymentManager = STPPaymentHandler.shared()
    paymentManager.confirmSetupIntent(setupIntentParams, authenticationContext: self, completion { (status, setupIntent, error) in
      switch (status) {
        case .succeeded:
          // Setup succeeded
        case .canceled:
          // Handle cancel
        case .failed:
          // Handle error
      }
    })
    STPSetupIntentParams *setupIntentParams = [[STPSetupIntentParams alloc] initWithClientSecret:client_secret];
    setupIntentParams.paymentMethodId = paymentMethodId;
    [[STPPaymentHandler sharedHandler] confirmSetupIntent:setupIntentParams
                            withAuthenticationContext:self
                                           completion:^(STPPaymentHandlerActionStatus status, STPSetupIntent * setupIntent, NSError * error) {
      switch (handlerStatus) {
        case STPPaymentHandlerActionStatusSucceeded:
          // Setup succeeded
        case STPPaymentHandlerActionStatusCanceled:
          // Handle cancel
        case STPPaymentHandlerActionStatusFailed:
          // Handle error
      }
    }];

    To complete the setup, create a SetupIntentParams object from the desired PaymentMethod and pass it with the current Activity to Stripe#confirmSetupIntent(). Some payment methods require additional authentication steps in order to complete payment. The SDK manages the payment confirmation and authentication flow, which may involve presenting additional screens required for authentication. For more information on 3D Secure authentication and customizing the authentication experience, see Supporting 3D Secure Authentication on Android.

    The result of the flow returns to your calling Activity via Activity#onActivityResult(). Handle the result by calling Stripe#onSetupResult() within Activity#onActivityResult(). The SetupIntentResult returned in ApiResultCallback#onSuccess() has two fields:

    • setupIntent: A SetupIntent object retrieved after confirmation and authentication
    • status: A SetupIntentResult.Status value that indicates the outcome of authentication
      • SUCCEEDED - confirmation or authentication succeeded
      • FAILED - confirmation or authentication failed
      • CANCELED - the customer canceled required authentication
    private void confirmSetupIntent(@NonNull String paymentMethodId) {
        final ConfirmSetupIntentParams confirmSetupIntentParams =
            ConfirmSetupIntentParams.create(paymentMethodId, clientSecret);
    
        // Optional: customize the Payment Authentication experience
        final StripeUiCustomization uiCustomization = new StripeUiCustomization();
        PaymentAuthConfig.init(new PaymentAuthConfig.Builder()
                .set3ds2Config(new PaymentAuthConfig.Stripe3ds2Config.Builder()
                        // set a 5 minute timeout for challenge flow
                        .setTimeout(5)
                        // customize the UI of the challenge flow
                        .setUiCustomization(uiCustomization)
                        .build())
                .build());
    
        mStripe.confirmSetupIntent(this, confirmSetupIntentParams);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mStripe.onSetupResult(
                requestCode, resultCode, data,
                new ApiResultCallback<SetupIntentResult>() {
                    @Override
                    public void onSuccess(@NonNull SetupIntentResult result) {
                        // If confirmation and authentication succeeded,
                        // the SetupIntent will have user actions resolved;
                        // otherwise, handle the failure as appropriate
                        // (e.g. the customer may need to choose a new payment
                        // method)
                        final SetupIntent setupIntent = result.getIntent();
                        final SetupIntent.Status status =
                            setupIntent.getStatus();
                        if (status == SetupIntent.Status.Succeeded) {
                            // show success UI
                        } else if (setupIntent.requiresConfirmation()) {
                            // handle confirmation
                        }
                    }
    
                    @Override
                    public void onError(@NonNull Exception e) {
                        // handle error
                    }
                });
    }
    private fun confirmPayment(paymentMethodId: String, clientSecret: String) {
        val confirmSetupIntentParams =
            ConfirmSetupIntentParams.create(paymentMethodId, clientSecret)
    
        // Optional: customize the Payment Authentication experience
        val uiCustomization = StripeUiCustomization()
        PaymentAuthConfig.init(PaymentAuthConfig.Builder()
            .set3ds2Config(PaymentAuthConfig.Stripe3ds2Config.Builder()
                // set a 5 minute timeout for challenge flow
                .setTimeout(5)
                // customize the UI of the challenge flow
                .setUiCustomization(uiCustomization)
                .build())
            .build())
    
        stripe.confirmPayment(this, confirmSetupIntentParams)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        super.onActivityResult(requestCode, resultCode, data)
        stripe.onPaymentResult(
            requestCode, resultCode, data,
            object : ApiResultCallback<SetupIntentResult> {
                override fun onSuccess(result: SetupIntentResult) {
                    // If confirmation and authentication succeeded,
                    // the SetupIntent will have user actions resolved;
                    // otherwise, handle the failure as appropriate
                    // (e.g. the customer may need to choose a new payment
                    // method)
                    val setupIntent = result.intent
                    val status = setupIntent.status
                    if (status == StripeIntent.Status.Succeeded) {
                        // show success UI
                    } else if (setupIntent.requiresConfirmation()) {
                        // handle confirmation
                    }
                }
    
                override fun onError(e: Exception) {
                    // handle error
                }
            })
    }

    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(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:

    Questions?

    We're always happy to help with code or other questions you might have. Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page