PaymentIntents Migration Guide Beta

    Learn how to migrate an existing integration to PaymentIntents.

    PaymentIntents introduce a new flow for accepting payments with Stripe. Instead of sending tokenized payment information to your server and manually creating a charge object, you start by creating a PaymentIntent on your server and then initiate completion of the payment process on the client side with Stripe.js or one of Stripe’s mobile SDKs. As this approach inverts the previous model, you may need to reorder key pieces of your existing token or source-based Stripe integration in order to adopt PaymentIntents.

    Before you begin, you may wish to review the PaymentIntents overview to learn about the lifecycle of a PaymentIntent. A PaymentIntent is an object that represents your intent to collect payment from a customer. It tracks the lifecycle of the payment through each stage of the process. Understanding how a PaymentIntent progresses through each stage of its lifecycle provides important context as you decide how to migrate your existing integration.

    Comparing the PaymentIntent flow to a traditional integration

    A traditional token-based Stripe integration that uses Elements to accept payment with credit cards typically includes the following steps:

    • Collect the customer’s payment information in the browser with Elements
    • Tokenize the payment information with Stripe.js
    • Perform a request to send the token to your server
    • Use the token to create a charge on your server with the desired amount and currency
    • If payment is successful, fulfill the customer’s order

    By comparison, a comparable PaymentIntent-based integration typically includes the following steps:

    • Create a PaymentIntent on your server with the desired amount and currency
    • Send the PaymentIntent’s client secret to the client side
    • Collect the customer’s payment information in the browser with Elements
    • Use Stripe.js or the mobile SDKs to perform Dynamic 3D Secure and complete the payment on the client side
    • Use webhooks on your server to fulfill the customer’s order if the payment is successful

    Migrating your Stripe integration

    If your Stripe integration does not yet use Stripe.js v3 and Elements, you may wish to first update your integration accordingly before adopting PaymentIntents. It is, however, still possible to use PaymentIntents without Elements by creating a source on the client side and attaching it to the PaymentIntent.

    Create the PaymentIntent on your server

    To handle a customer’s payment, you must first create a PaymentIntent. Each PaymentIntent typically correlates with a single “cart” or customer session in your application. On the server side, applications generally create a PaymentIntent when the customer begins their checkout process. You can correlate it with the customer’s cart or session and reuse it as needed until payment is collected successfully.

    When you create a PaymentIntent, specify the currency, permitted source types, and the amount of money that you wish to collect from the customer. The following example shows how to create a PaymentIntent on your server:

    curl https://api.stripe.com/v1/payment_intents \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
       -d amount=1099 \
       -d currency=usd \
       -d allowed_source_types[]=card
    
    # 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',
      allowed_source_types: ['card']
    })
    
    # 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',
      allowed_source_types=['card']
    )
    
    // 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");
    
    StripePaymentIntent::create([
      "amount" => 1099,
      "currency" => "usd",
      "allowed_source_types" => ["card"],
    ]);
    
    // 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 allowed_source_types = new ArrayList();
    allowed_source_types.add("card");
    paymentintentParams.put("allowed_source_types", allowed_source_types);
    
    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',
        allowed_source_types: ['card'],
      });
    })();
    
    // 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)),
      AllowedSourceTypes: []*string{
        stripe.String("card"),
      },
    }
    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",
      AllowedSourceTypes = new List<string> { "card" }
    };
    paymentIntents.Create(createOptions);
    

    If the amount that you are collecting from the customer changes later, you can update the PaymentIntent object accordingly.

    Make the PaymentIntent’s client secret available on the client side

    The PaymentIntent contains a client secret, a key that is unique to the individual PaymentIntent. Your application must make the client secret accessible on the client side so that you can use it to complete the customer’s payment. In an application that uses server-side rendering, you can embed the client secret in a data attribute and then extract it with JavaScript in order to use it for a charge.

    <!-- Checkout Page -->
    <button id="purchase" data-secret="<%= intent.client_secret %>">Purchase</button>
    
    <script>
    const button = document.getElementById("purchase");
    button.addEventListener("click", async ev => {
      const clientSecret = button.dataset.secret;
      // Continue to the next step
    });
    </script>
    

    Completing payment on the client side

    Existing integrations typically use the createToken or createSource functions in Stripe.js to tokenize payment information collected by the Card Element. The tokenized payment information can then be sent to the server, where it is used to create a charge. In a PaymentIntents integration, both of those steps are replaced with a single step. The charge is completed on the client side by calling the handleCardPayment function. This function also automatically walks the customer through any required source actions, such as 3DS authorization.

    The following example demonstrates how to update the code that runs when the user submits their payment in a sample integration:

    Before:

    button.addEventListener('click', function(ev) {
      stripe.createToken(cardElement, {
        name: 'Jane Doe',
        email: 'janedoe@example.com',
        address_line1: '123 Foo St.',
        address_zip: '94103',
        address_country: 'US'
      }).then(function(result) {
        if (result.error) {
          // Display error.message in your UI.
        } else {
          fetch('/charge', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({token})
          }).then(function(response) {
            // Handle your server's response.
          });
        }
      });
    });
    button.addEventListener('click', async (ev) => {
      const {token, error} = await stripe.createToken(cardElement, {
        name: 'Jane Doe',
        email: 'janedoe@example.com',
        address_line1: '123 Foo St.',
        address_zip: '94103',
        address_country: 'US'
      });
    
      if (error) {
        // Display error.message in your UI.
      } else {
        const response = await fetch('/charge', {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({token})
        });
    
        // Handle your server's response.
      }
    });

    After:

    button.addEventListener('click', function(ev) {
      var clientSecret = button.dataset.secret;
      stripe.handleCardPayment(
        clientSecret, cardElement, {
          source_data: {
            owner: {
              name: 'Jane Doe',
              email: 'janedoe@example.com',
              address: {
                line1: '123 Foo St.',
                postal_code: '94103',
                country: 'US'
              }
            }
          }
        }
      ).then(function(result) {
        if (result.error) {
          // Display result.error.message in your UI.
        } else {
          // The payment has succeeded. Display a success message.
        }
      });
    });
    button.addEventListener('click', async (ev) => {
      const clientSecret = button.dataset.secret;
      const {paymentIntent, error} = await stripe.handleCardPayment(
        clientSecret, cardElement, {
          source_data: {
            owner: {
              name: 'Jane Doe',
              email: 'janedoe@example.com',
              address: {
                line1: '123 Foo St.',
                postal_code: '94103',
                country: 'US'
              }
            }
          }
        }
      );
    
      if (error) {
        // Display error.message in your UI.
      } else {
        // The payment has succeeded. Display a success message.
      }
    });

    The API endpoint where the original example sends the token is no longer needed after adopting PaymentIntents. When you call the handleCardPayment function, Stripe.js securely sends the payment information to Stripe, resulting in the automatic creation of a charge. You do not need to create the charge on your server.

    Using webhooks to fulfill orders after accepting payment

    As Stripe creates the charge for you when Stripe.js handles the payment on the client side, your server must rely on webhooks to learn when payment completes successfully. This differs from current integrations, where your application is responsible for manually creating the charge. PaymentIntents use this flow in order to make your integration ready for asynchronous payments, including 3D Secure.

    If you would like your application to perform steps like order fulfillment after a customer’s payment is successful, you should implement support for webhooks and monitor the payment_intent.succeeded event. Business logic that your application performs after charge creation can be moved to the webhook event handler instead.

    Next steps

    Now you have all the information you need to begin migrating an existing Stripe integration to PaymentIntents. 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.