Off-session Payments with Payment Intents

    Learn how to save a customer’s payment information and create a payment later when the customer is not available to complete authentication steps such as 3D Secure.

    When using a PaymentIntentThe Payment Intents API is a new way to build dynamic payment flows. It tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods. to accept payment from a customer, the final step is confirmingConfirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment. the payment. In some scenarios, this occurs while the customer is actively using your application. In other cases, however, you may need to charge the customer when they are not actively using your application. This is called an “off-session” payment.

    When your integration uses off-session payments, your application becomes responsible for ensuring that customers perform any additional steps that are required to complete the process. This is an important consideration for integrations that need to comply with the upcoming Strong Customer AuthenticationStrong Customer Authentication (SCA) is a new regulatory requirement coming into effect on September 14, 2019 which will impact many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase. rules in Europe. Some steps, such as 3D Secure3D Secure provides an additional layer of authentication for credit card transactions that protects merchants from liability for fraudulent card payments. During a transaction that incorporates the 3D Secure authorization process, the customer is prompted to supply a separate password or code to validate their purchase. authentication, may still need to be performed on the client. In such cases, you will need to build a way to notify your customers via email or push notifications and ask them to return to your application to complete authentication.

    To collect off-session payments with the Payment Intents API, perform the following steps:

    1. Collect payment information and attach it to a customer
    2. At a later time, create a PaymentIntent and attach a payment method
    3. Check the PaymentIntent status
    4. Notify customer to complete payment if needed

    Step 1: Collect payment information and attach it to a customer

    Start by securely collecting the customer’s payment information with Stripe.js Elements or the mobile SDKs. Once you have payment information, create a Customer and attach the payment method.

    curl https://api.stripe.com/v1/customers \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d email="paying.user@example.com"
    
    # Attach payment method to the customer:
    curl https://api.stripe.com/v1/payments_methods/pm_123456789/attach \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d customer=cus_EkSi7VpNjXHSHQ
    
    # YOUR CODE: Save the customer ID and other info in a database for later.
    
    # 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'
    
    # Create a Customer:
    customer = Stripe::Customer.create({
        email: 'paying.user@example.com',
    })
    
    # Attach payment method to the customer:
    Stripe::PaymentMethod.attach('pm_123456789', {
      customer: customer.id,
    })
    
    # YOUR CODE: Save the customer ID and other info in a database for later.
    
    # 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"
    
    # Create a Customer:
    customer = stripe.Customer.create(
      email='paying.user@example.com',
    )
    
    # Attach payment method to the customer:
    stripe.PaymentMethod.attach('pm_123456789', customer=customer.id)
    
    # YOUR CODE: Save the customer ID and other info in a database for later.
    
    // 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");
    
    // Create a Customer:
    $customer = \Stripe\Customer::create([
        'email' => 'paying.user@example.com',
    ]);
    
    // Attach payment method to the customer:
    StripePaymentMethod::attach("pm_123456789", [
      "customer" => $customer->id,
    ]);
    
    // YOUR CODE: Save the customer ID and other info in a database for later.
    
    // 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";
    
    // Create a Customer:
    Map<String, Object> customerParams = new HashMap<>();
    customerParams.put("email", "paying.user@example.com");
    Customer customer = Customer.create(customerParams);
    
    // Attach payment method to the customer:
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("customer", customer.getId());
    PaymentMethod.attach("pm_123456789", params);
    
    // YOUR CODE: Save the customer ID and other info in a database for later.
    
    // 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
    var stripe = require("stripe")("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    (async () => {
      // Create a Customer:
      const customer = await stripe.customers.create({
        email: 'paying.user@example.com',
      });
    
      // Attach payment method to the customer:
      const { err, paymentMethod } = await stripe.paymentMethods.attach("pm_123456789", { customer: customer.id });
    
      // YOUR CODE: Save the customer ID and other info in a database for later.
    })();
    
    // 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"
    
    // Create a Customer:
    customerParams := &stripe.CustomerParams{
        Email: stripe.String("paying.user@example.com"),
    }
    cus, _ := customer.New(customerParams)
    
    // Attach payment method to the customer:
    params := &stripe.PaymentMethodAttachParams{
      Customer: customer.ID,
    }
    PaymentMethod, err := paymentMethod.Attach("pm_123456789", params)
    
    // YOUR CODE: Save the customer ID and other info in a database for later.
    
    // 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.SetApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    // Create a Customer:
    var customerOptions = new CustomerCreateOptions {
        Email = "paying.user@example.com",
    };
    var customerService = new CustomerService();
    Customer customer = customerService.Create(customerOptions);
    
    // Attach payment method to the customer:
    var paymentMethodService = new PaymentMethodService();
    var attachOptions = new PaymentMethodAttachOptions {
      Customer = customer.Id
    };
    paymentMethodService.Attach("pm_123456789", attachOptions);
    

    Step 2: At a later time, 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 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}',
      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}}',
      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}}",
      "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("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
    var 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}}",
        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,
    }
    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.SetApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    var paymentIntents = new PaymentIntentService();
    var createOptions = new PaymentIntentCreateOptions {
      Amount = 1099,
      Currency = "usd",
      PaymentMethodTypes = new List<string> { "card" },
      Customer = "{{CUSTOMER_ID}}",
      PaymentMethod = "{{PAYMENT_METHOD_ID}}",
      Confirm = true,
    };
    paymentIntents.Create(createOptions);
    

    Step 3: Check the PaymentIntent status

    At this point, the request may fail with a 402 HTTP status code, meaning that the payment was unsuccessful—check the last_payment_error property and attempt to try again, notifying the customer and collect new payment information if necessary.

    Otherwise, inspect the status property of the newly-created PaymentIntent to determine if the payment completed successfully. The following list describes possible status values and their significance:

    • requires_action: notify the customer to return to your application to complete payment.
    • succeeded: the payment completed and resulted in the creation of a charge using the supplied payment method. No further steps are required.

    Step 4: Notify customer to complete payment if needed

    When a payment fails (requires_payment_method) or requires more authentication (requires_action), you should notify your customer to return to your application to complete the payment. We recommend that you create a web page or mobile app screen with a payment form that you can send your customer to in these cases.

    On this page, make the PaymentIntent’s client secretThe client secret is a unique key associated with a single PaymentIntent. It is passed to the client side where it can be used in Stripe.js to complete the payment process. available and use the Stripe.js or the mobile SDKs to display authentication.

    While testing, you can use our 3D Secure required test card number (4000000000003063) to force this flow. Using the 3D Secure not required card (4242424242424242) will skip this part of the flow and complete at Step 3 because it does not require authentication.

    In a Stripe.js integration with automatic confirmation, pass the client secret to the handleCardPayment function, which automatically ushers the customer through the authentication process and initiates completion of the payment. You can refer to the Android and iOS SDK documentation for details about how to handle next steps in a mobile application.

    stripe.handleCardPayment({{CLIENT_SECRET}})
      .then(function(result) {
        if (result.error) {
          // Display error.message in your UI.
        } else {
          // The payment has succeeded. Display a success message.
        }
      });
    (async () => {
      const {paymentIntent, error} = await stripe.handleCardPayment({{CLIENT_SECRET}});
      if (error) {
        // Display error.message in your UI.
      } else {
        // The payment has succeeded. Display a success message.
      }
    })();

    As authentication and other required steps occur asynchronously on the client, you must monitor for the payment_intent.succeeded and payment_intent.payment_failed events on your server in order to determine the outcome of the process and handle fulfillment upon success.

    In a Stripe.js integration with manual confirmation, pass the client secret to the handleCardAction function, which triggers the UI to handle authentication. If successful, the PaymentIntent will have a status of requires_confirmation and you will need to confirm the PaymentIntent again on your server to finish the payment.

    stripe.handleCardAction(
      {{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);
      }
    });
    (async () => {
      const { error: errorAction, paymentIntent } =
        await stripe.handleCardAction({{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());
      }
    })();

    Now that the customer is no longer off-session, refer to Step 4 of the Manual Confirmation Quickstart for instructions on how to confirm the PaymentIntent again and fulfill the order.

    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