Charging Saved Cards

    Learn how to collect payments from a customer after you save their card details.

    After saving a customer’s card details with Stripe, you can charge them in the future. How you collect these payments depends on when you charge your customer:

    • On-session: A payment that occurs while your customer is actively using your application is called an “on-session” payment. You customer may be prompted for authentication.
    • Off-session: A payment that occurs after your customer stops interacting with your application is called an “off-session” payment. If authentication is required, you customer needs to return to your application to complete the payment.

    It is important to treat these cases separately. Because your customer may not be available to perform additional steps, the way you handle failures in each case is different.

    On-session payments with saved cards

    When charging a customer on-session, the regional regulations applicable to the card or your Radar rules may require that you authenticate the cardholder. Follow these steps to ensure you handle authentication when required:

    1. Create a PaymentIntent on the server
    2. Pass the PaymentIntent’s client secret to the client
    3. Submit the payment to Stripe from the client

    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.

    When creating an on-session PaymentIntent, specify both the ID of the Customer and the ID of the previously saved Card, Source, or PaymentMethod. For manual confirmation, also set confirmation_method to manual.

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd \
      -d customer="{{CUSTOMER_ID}}" \
      -d payment_method="{{PAYMENT_METHOD_ID}}"
    
    # 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'
    
    intent = Stripe::PaymentIntent.create({
        amount: 1099,
        currency: 'usd',
        customer: '{{CUSTOMER_ID}}',
        payment_method: '{{PAYMENT_METHOD_ID}}',
    })
    
    # 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'
    
    intent = stripe.PaymentIntent.create(
      amount=1099,
      currency='usd',
      customer='{{CUSTOMER_ID}}',
      payment_method='{{PAYMENT_METHOD_ID}}',
    )
    
    // 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\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    $intent = \Stripe\PaymentIntent::create([
        'amount' => 1099,
        'currency' => 'usd',
        'customer' => '{{CUSTOMER_ID}}',
        'payment_method' => '{{PAYMENT_METHOD_ID}}',
    ]);
    
    // 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.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> paymentintentParams = new HashMap<String, Object>();
    paymentintentParams.put("amount", 1099);
    paymentintentParams.put("currency", "usd");
    paymentintentParams.put("customer", "{{CUSTOMER_ID}}");
    paymentintentParams.put("payment_method", "{{PAYMENT_METHOD_ID}}")
    
    PaymentIntent.create(paymentintentParams);
    
    // 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
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1099,
        currency: 'usd',
        customer: '{{CUSTOMER_ID}}',
        payment_method: '{{PAYMENT_METHOD_ID}}',
      });
    })();
    
    // 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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      PaymentMethod: stripe.String("{{PAYMENT_METHOD_ID}}"),
    }
    paymentintent.New(params)
    
    // 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
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
        CustomerId = "{{CUSTOMER_ID}}",
        PaymentMethodId = "{{PAYMENT_METHOD_ID}}",
    };
    service.Create(options);
    

    The PaymentIntent’s initial status is requires_confirmation. This indicates that you should now confirm the PaymentIntent from the client.

    Step 2: Pass the PaymentIntent’s client secret to the client

    The PaymentIntent object contains a client secret, a unique key that you need to pass to Stripe.js on the client side in order to create a charge. If your application uses server-side rendering, use your template framework to embed the client secret in the page using a data attribute or a hidden HTML element.

    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="<%= @intent.client_secret %>">Submit Payment</button>
    get '/checkout' do
        @intent = # ... Fetch or create the PaymentIntent
        erb :checkout
    end
    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="{{ client_secret }}">
      Submit Payment
    </button>
    @app.route('/checkout')
    def checkout():
      intent = # ... Fetch or create the PaymentIntent
      return render_template('checkout.html', client_secret=intent.client_secret)
    <?php
        $intent = # ... Fetch or create the PaymentIntent;
    ?>
    ...
    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="<?= $intent->client_secret ?>">
      Submit Payment
    </button>
    ...
    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="{{ client_secret }}">
      Submit Payment
    </button>
    import java.util.HashMap;
    import java.util.Map;
    
    import com.stripe.model.PaymentIntent;
    
    import spark.ModelAndView;
    
    import static spark.Spark.get;
    
    public class StripeJavaQuickStart {
      public static void main(String[] args) {
        get("/checkout", (request, response) -> {
          PaymentIntent intent = // ... Fetch or create the PaymentIntent
    
          Map<String, String> map = new HashMap();
          map.put("client_secret", intent.getClientSecret());
    
          return new ModelAndView(map, "checkout.hbs");
        }, new HandlebarsTemplateEngine());
      }
    }
    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="{{ client_secret }}">
      Submit Payment
    </button>
    const express = require('express');
    const expressHandlebars = require('express-handlebars');
    const app = express();
    
    app.engine('.hbs', expressHandlebars({ extname: '.hbs' }));
    app.set('view engine', '.hbs');
    app.set('views', './views');
    
    app.get('/checkout', async (req, res) => {
      const intent = // ... Fetch or create the PaymentIntent
      res.render('checkout', { client_secret: intent.client_secret });
    });
    
    app.listen(3000, () => {
      console.log('Running on port 3000')
    });
    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="{{ .ClientSecret }}">
      Submit Payment
    </button>
    package main
    
    import (
      "html/template"
      "net/http"
    
      stripe "github.com/stripe/stripe-go"
    )
    
    type CheckoutData struct {
      ClientSecret string
    }
    
    func main() {
      checkoutTmpl := template.Must(template.ParseFiles("views/checkout.html"))
    
      http.HandleFunc("/checkout", func(w http.ResponseWriter, r *http.Request) {
        intent := // ... Fetch or create the PaymentIntent
        data := CheckoutData{
          ClientSecret: intent.ClientSecret,
        }
        checkoutTmpl.Execute(w, data)
      })
    
      http.ListenAndServe(":3000", nil)
    }
    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button" data-secret="@ViewData["ClientSecret"]">
      Submit Payment
    </button>
    using System;
    using Microsoft.AspNetCore.Mvc;
    using Stripe;
    
    namespace StripeExampleApi.Controllers
    {
        [Route("/[controller]")]
        public class CheckoutController : Controller
        {
            public IActionResult Index()
            {
              var intent = // ... Fetch or create the PaymentIntent
              ViewData["ClientSecret"] = intent.ClientSecret;
              return View();
            }
        }
    }

    The client secret can be used 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 the app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint you set up in step 1 to create a PaymentIntent.

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

    The client secret can be used 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.

    The PaymentIntent object contains a client secret, a unique key that you pass to the app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint 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());
    
          // Now retrieve the PaymentIntent that was created on your backend
          final String clientSecret =
              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)
          // Now retrieve the PaymentIntent that was created on your backend
          val clientSecret = myBackendApiClient.createPaymentIntent(
              amount: 100, currency: "usd", setupFutureUsage: "off_session",
              object: ApiResultCallback<String> {
                  override fun onSuccess(clientSecret: String) {
                      // Hold onto the client secret for Step 4
                  }
    
                  override fun onError(e: Exception) {
                      // handle error
                  }
          })
      }
    }

    The client secret can be used 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: 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 cardButton = document.getElementById('card-button');
    var clientSecret = cardButton.dataset.secret;
    
    cardButton.addEventListener('click', function(ev) {
      stripe.handleCardPayment(
        clientSecret,
        {
          payment_method: '{PAYMENT_METHOD_ID}',
        }
      ).then(function(result) {
        if (result.error) {
          // Display error.message in your UI.
        } else {
          // The payment has succeeded. Display a success message.
        }
      });
    });
    const cardButton = document.getElementById('card-button');
    const clientSecret = cardButton.dataset.secret;
    
    cardButton.addEventListener('click', async (ev) => {
      const {paymentIntent, error} = await stripe.handleCardPayment(
        clientSecret,
        {
          payment_method: '{PAYMENT_METHOD_ID}',
        }
      );
    
      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 customer 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 your customer to leave the page after payment is complete but before the fulfillment process initiates. Instead, handle asynchronous events to drive fulfillment server-side when the payment succeeds.

    There are several ways you can fulfill an order:

    To complete the payment, create an STPPaymentIntentParams instance with the client secret from step 2 and pass it 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 the STPAuthenticationContext protocol.

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
    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];
    [[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 customer 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 your customer to leave the app after payment is complete but before the fulfillment process initiates. Instead, handle asynchronous events to drive fulfillment server-side 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 retrieved after confirmation and 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 clientSecret) {
        final ConfirmPaymentIntentParams confirmPaymentIntentParams =
            ConfirmPaymentIntentParams.create(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 succeeds,
                        // the PaymentIntent has next 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(clientSecret: String) {
        val confirmPaymentIntentParams =
            ConfirmPaymentIntentParams.create(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 is 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.

    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 PaymentIntent, 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) { 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 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.

    Some payment methods require additional authentication steps in order to complete a payment. Check the status of the PaymentIntent object returned from confirmation.

    If its 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 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(paymentIntent);
                }
    
                @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);
        } 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)
        } 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
                }
            })
    }

    Next, test your integration to make sure you correctly handle cards that require additional authentication.

    Off-session payments with saved cards

    Follow these steps to use off-session PaymentIntents to collect payment from your existing customer:

    1. Create a PaymentIntent with a payment method
    2. Check the PaymentIntent status
    3. Notify customer to complete payment if needed

    Step 1: Create a PaymentIntent with a payment method

    When creating a PaymentIntent, specify both the ID of the Customer and the ID of the previously saved Card, Source, or PaymentMethod. You should also set the value of the PaymentIntent’s confirm property to true, which causes confirmation to occur immediately when the PaymentIntent is created. Note that you can also programmatically confirm a PaymentIntent that has already been created.

    The following code example demonstrates how to create and confirm a PaymentIntent with a customer and payment_method:

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd \
      -d payment_method_types[]=card \
      -d customer="{{CUSTOMER_ID}}" \
      -d payment_method="{{PAYMENT_METHOD_ID}}" \
      -d off_session=true \
      -d confirm=true
    
    # 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'
    
    intent = Stripe::PaymentIntent.create({
        amount: 1099,
        currency: 'usd',
        payment_method_types: ['card'],
        customer: '{CUSTOMER_ID}',
        payment_method: '{PAYMENT_METHOD_ID}',
        off_session: true,
        confirm: true,
    })
    
    # 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',
      payment_method_types=['card'],
      customer='{{CUSTOMER_ID}}',
      payment_method='{{PAYMENT_METHOD_ID}}',
      off_session=True,
      confirm=True,
    )
    
    // 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\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    \Stripe\PaymentIntent::create([
        'amount' => 1099,
        'currency' => 'usd',
        'payment_method_types' => ['card'],
        'customer' => '{{CUSTOMER_ID}}',
        'payment_method' => '{{PAYMENT_METHOD_ID}}',
        'off_session' => true,
        'confirm' => true,
    ]);
    
    // 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.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> paymentintentParams = new HashMap<String, Object>();
    paymentintentParams.put("amount", 1099);
    paymentintentParams.put("currency", "usd");
    ArrayList payment_method_types = new ArrayList();
    payment_method_types.add("card");
    paymentintentParams.put("payment_method_types", payment_method_types);
    paymentintentsParams.put("customer", "{{CUSTOMER_ID}}");
    paymentintentsParams.put("payment_method", "{{PAYMENT_METHOD_ID}}");
    paymentIntentsParams.put("off_session", true);
    paymentIntentsParams.put("confirm", true);
    
    PaymentIntent.create(paymentintentParams);
    
    // 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
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1099,
        currency: 'usd',
        payment_method_types: ['card'],
        customer: '{{CUSTOMER_ID}}',
        payment_method: '{{PAYMENT_METHOD_ID}}',
        off_session: true,
        confirm: true,
      });
    })();
    
    // 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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      PaymentMethodTypes: stripe.StringSlice([]string{
        "card",
      }),
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      PaymentMethod: stripe.String("{{PAYMENT_METHOD_ID}}"),
      Confirm: true,
      OffSession: true,
    }
    paymentintent.New(params)
    
    // 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
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
        PaymentMethodTypes = new List<string> { "card" },
        CustomerId = "{{CUSTOMER_ID}}",
        PaymentMethodId = "{{PAYMENT_METHOD_ID}}",
        Confirm = true,
        OffSession = true,
    };
    service.Create(options);
    

    Step 2: Check the PaymentIntent status

    Inspect the status property of the PaymentIntent to confirm that the payment completed successfully.

    If the payment attempt succeeded, the PaymentIntent will have a status of succeeded and the off-session payment is complete.

    Otherwise, if the payment attempt requires authentication, the request will fail with a 402 HTTP status code and the status of the PaymentIntent will be requires_payment_method. You will have to notify your customer to complete the payment.

    The payment may fail for other reasons unrelated to authentication (e.g., an expired card or insufficient funds) so build a recovery flow that handles a wide range of declines.

    Step 3: Notify customer to complete payment if needed

    When a payment attempt fails, notify your customer to return to your application to complete the PaymentIntent. You can check the last_payment_error on the PaymentIntent to inspect why the payment attempt failed off-session.

    If the payment attempt failed due to an authentication_required decline code, you can give the customer the option to try paying again on-session with the same payment method. We recommend creating a separate web page to handle this case in order to communicate why the payment failed initially. You can then follow the on-session payment instructions from step 2 onwards to complete the payment.

    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 are able to accept off-session payments with the Payment Intents API. 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