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. Your 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, your customer needs to return to your application to complete the payment. Off-session payments made with cards saved before September 14, 2019, are eligible for grandfathering.

    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

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

    Need a synchronous flow? Manual confirmation lets you finish the payment on the server and immediately run any post-payment logic.

    When creating an on-session PaymentIntent, specify both the ID of the Customer and the ID of the previously saved Card, Source, or PaymentMethod.

    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

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

    • paymentIntent: A PaymentIntent object retrieved after confirmation and 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());
    }
    
    private void confirmPayment(@NonNull String clientSecret) {
        final ConfirmPaymentIntentParams confirmPaymentIntentParams =
            ConfirmPaymentIntentParams.create(clientSecret);
        mStripe.confirmPayment(this, confirmPaymentIntentParams);
    }
    
    @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 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
                }
            });
    }
    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
        )
    }
    
    private fun confirmPayment(clientSecret: String) {
        stripe.confirmPayment(
            this,
            ConfirmPaymentIntentParams.create(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:

    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.
    • Set off_session to true to indicate that the customer is not in your checkout flow during this payment attempt, and is therefore is unable to authenticate.
    • 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.

    If the payment attempt failed due to an authentication_required decline code, the request also fails 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. See the following step for more details.

    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. 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, 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:

    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