Saving Card Details Without a Payment

    Learn how to save card details and meet regulatory requirements like Strong Customer Authentication.

    Use the Setup Intents API to collect card details up front, with the final amount or payment date to be determined later. 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, create an STPPaymentMethod from the PKPayment.

    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 STPSetupIntentConfirmParams 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 STPSetupIntentConfirmParams object to the confirmSetupIntent method on a STPPaymentHandler sharedManager. If the customer must perform additional steps to complete the payment, such as authentication, STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks them through that process. See Supporting 3D Secure Authentication on iOS to learn more.

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

    let setupIntentParams = STPSetupIntentConfirmParams(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
      }
    })
    STPSetupIntentConfirmParams *setupIntentParams = [[STPSetupIntentConfirmParams 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
      • TIMEDOUT - the authentication attempt timed-out
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Optional: customize the payment authentication experience.
        // PaymentAuthConfig.init() must be called before Stripe object
        // is instantiated.
        final PaymentAuthConfig.Stripe3ds2UiCustomization uiCustomization =
            new PaymentAuthConfig.Stripe3ds2UiCustomization.Builder()
                .build();
        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());
    
        PaymentConfiguration.init("pk_test_TYooMQauvdEDq54NiTphI7jx");
        mStripe = new Stripe(this,
            PaymentConfiguration.getInstance().getPublishableKey());
    
        // now retrieve the PaymentIntent that was created on your backend
    }
    
    private void confirmSetupIntent(@NonNull String paymentMethodId) {
        mStripe.confirmSetupIntent(
            this,
            ConfirmSetupIntentParams.create(paymentMethodId, clientSecret)
        );
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mStripe.onSetupResult(requestCode, 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
                }
            });
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        // Optional: customize the payment authentication experience.
        // PaymentAuthConfig.init() must be called before Stripe object
        // is instantiated.
        val uiCustomization = PaymentAuthConfig.Stripe3ds2UiCustomization.Builder()
            .build()
        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())
    
        PaymentConfiguration.init("pk_test_TYooMQauvdEDq54NiTphI7jx")
        stripe = Stripe(
            this,
            PaymentConfiguration.getInstance().publishableKey
        )
    
        // now retrieve the PaymentIntent that was created on your backend
    }
    
    private fun confirmSetupIntent(paymentMethodId: String, clientSecret: String) {
        stripe.confirmPayment(
            this,
            ConfirmSetupIntentParams.create(paymentMethodId, clientSecret)
        )
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        super.onActivityResult(requestCode, resultCode, data)
        stripe.onSetupResult(requestCode, 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:

    Was this page helpful?

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

    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.

    On this page