Saving Card Details After a Payment

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

    Use the Payment Intents API to charge a card and save the details for future payment:

    • 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

    The Payment Intents API completes the payment on the client and relies on webhooks to fulfill any purchases.

    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 in the head section on every page on your site. This script must always load directly from js.stripe.com to remain PCI compliant. You can’t include the script 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 and ability to detect anomalous browsing behavior, include the script in the head section on every page on your site, not just the checkout page.

    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, create an STPPaymentMethod from the PKPayment.

    Only save Apple Pay as a payment method for subscription purchases. Attempting to reuse Apple Pay for a non-subscription payment can result in it being declined.

    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

    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.
          // Then, send the payment method from the SetupIntent to your
          // server in order to attach the payment method to a customer.
        }
      });
    });
    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.
        // Then, send the payment method from the SetupIntent to your
        // server in order to attach the payment method to a customer.
      }
    });

    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 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 should present any additional view controllers and implements STPAuthenticationContext.

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: <# client secret from step 2 #>)
    paymentIntentParams.paymentMethodId = <# STPPaymentMethod from step 3 #>.stripeId
    let paymentManager = STPPaymentHandler.shared()
    paymentManager.confirmPayment(paymentIntentParams, with: paymentContext) { (status, paymentIntent, error) in
        switch (status) {
        case .succeeded:
            // Payment succeeded
            break
        case .canceled:
            // Handle cancel
            break
        case .failed:
            // Handle error
            break
        }
    }
    STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:<# client secret from step 2 #>];
    paymentIntentParams.paymentMethodId = <# STPPaymentMethod from step 3 #>.stripeId;
    [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams
                            withAuthenticationContext:self
                                           completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * error) {
      switch (handlerStatus) {
        case STPPaymentHandlerActionStatusSucceeded:
          // Payment succeeded
          break;
        case STPPaymentHandlerActionStatusCanceled:
          // Handle cancel
          break;
        case STPPaymentHandlerActionStatusFailed:
          // Handle error
          break;
      }
    }];

    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 ConfirmPaymentIntentParams 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 getters:

    • getIntent() - a PaymentIntent object that has been retrieved after confirmation/authentication
    • getOutcome() - a StripeIntentResult.Outcome 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
      • TIMEDOUT - the payment 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 confirmPayment(@NonNull String paymentMethodId) {
        mStripe.confirmPayment(this,
            ConfirmPaymentIntentParams.createWithPaymentMethodId(
                paymentMethodId,
                clientSecret
            )
        );
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mStripe.onPaymentResult(requestCode, 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
                }
            });
    }
    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 confirmPayment(paymentMethodId: String, clientSecret: String) {
        stripe.confirmPayment(
            this,
            ConfirmPaymentIntentParams.createWithPaymentMethodId(
                paymentMethodId,
                clientSecret
            )
        )
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        super.onActivityResult(requestCode, resultCode, data)
        stripe.onPaymentResult(requestCode, 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:

    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.

    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