Using Payment Intents with Manual Confirmation on Web

    Learn how to accept card payments with Elements and the Payment Intents API to make one-time payments.

    Use our automatic flow to get started with the Payment Intents API faster.

    If you can’t use webhooks, accept card payments by confirming the payment on the server and fulfilling on your server immediately after the payment succeeds.

    Manual confirmation quickstart

    Manual confirmation lets you confirm the PaymentIntent on the server. It’s the easiest way to migrate from the Charges API, but requires extra trips to your server to manually handle authentication flows like 3D Secure.

    A diagram showing the manual confirmation client to server with optional arrow pointing back to client

    1. Collect payment method details on the client
    2. Create a PaymentIntent on the server
    3. Handle any next actions on the client
    4. Confirm the PaymentIntent again on the server

    Step 1: Collect payment method details on the client

    The Payment Intents API works with Stripe.js & Elements to securely collect payment information on the client side. To get started with Elements, include the following script on your pages. This script must always load directly from js.stripe.com in order to remain PCI compliant—you can’t include it in a bundle or host a copy of it yourself.

    <script src="https://js.stripe.com/v3/"></script>
    

    To securely collect card details from your customers, Elements creates UI components for you that are hosted by Stripe. They are then placed into your payment form, rather than you creating them directly. To determine where to insert these components, create empty DOM elements (containers) with unique IDs within your payment form.

    <input id="cardholder-name" type="text">
    <!-- placeholder for Elements -->
    <div id="card-element"></div>
    <button id="card-button">Submit Payment</button>
    

    Next, create an instance of the Stripe object, providing your publishable API key as the first parameter. Afterwards, create an instance of the Elements object and use it to mount a Card element in the relevant placeholder in the page.

    var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    var elements = stripe.elements();
    var cardElement = elements.create('card');
    cardElement.mount('#card-element');
    const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    const elements = stripe.elements();
    const cardElement = elements.create('card');
    cardElement.mount('#card-element');

    Finally, use stripe.createPaymentMethod on your client to collect the card details and create a PaymentMethod when the user clicks the submit button.

    var cardholderName = document.getElementById('cardholder-name');
    var cardButton = document.getElementById('card-button');
    
    cardButton.addEventListener('click', function(ev) {
      stripe.createPaymentMethod('card', cardElement, {
        billing_details: {name: cardholderName.value}
      }).then(function(result) {
        if (result.error) {
          // Show error in payment form
        } else {
          // Otherwise send paymentMethod.id to your server (see Step 2)
          fetch('/ajax/confirm_payment', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ payment_method_id: result.paymentMethod.id })
          }).then(function(result) {
            // Handle server response (see Step 3)
            result.json().then(function(json) {
              handleServerResponse(json);
            })
          });
        }
      });
    });
    
    
    const cardholderName = document.getElementById('cardholder-name');
    const cardButton = document.getElementById('card-button');
    
    cardButton.addEventListener('click', async (ev) => {
      const {paymentMethod, error} =
        await stripe.createPaymentMethod('card', cardElement, {
          billing_details: {name: cardholderName.value}
        });
      if (error) {
        // Show error in payment form
      } else {
        // Send paymentMethod.id to your server (see Step 2)
        const response = await fetch('/ajax/confirm_payment', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ payment_method_id: paymentMethod.id })
        });
    
        const json = await response.json();
    
        // Handle server response (see Step 3)
        handleServerResponse(json);
      }
    });
    
    

    Step 2: Create a PaymentIntent on the server

    Set up an endpoint on your server to receive the request. This endpoint will also be used in Step 4 to handle cards that require an extra step of authentication.

    Create a new PaymentIntent with the ID of the PaymentMethod created on your client. You can confirm the PaymentIntent by setting confirm property to true when the PaymentIntent is created or by calling confirm after creation. Separate authorization and capture is also supported for card payments.

    If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. If the payment failed, the status is set back to requires_payment_method and you should show an error to your user. If the payment doesn’t require any additional authentication then a charge is created and the PaymentIntent status is set to succeeded.

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d payment_method="{{PAYMENT_METHOD_ID}}" \
      -d amount=1099 \
      -d currency=usd \
      -d confirmation_method=manual \
      -d confirm=true
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    post '/ajax/confirm_payment' do
      data = JSON.parse(request.body.read.to_s)
    
      begin
        if data['payment_method_id']
          # Create the PaymentIntent
          intent = Stripe::PaymentIntent.create(
            payment_method: data['payment_method_id'],
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true,
          )
        elsif data['payment_intent_id']
          intent = Stripe::PaymentIntent.confirm(data['payment_intent_id'])
        end
      rescue Stripe::CardError => e
        # Display error on client
        return [200, { error: e.message }.to_json]
      end
    
      return generate_payment_response(intent)
    end
    
    def generate_payment_response(intent)
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action' &&
          intent.next_action.type == 'use_stripe_sdk'
        # Tell the client to handle the action
        [
          200,
          {
            requires_action: true,
            payment_intent_client_secret: intent.client_secret
          }.to_json
        ]
      elsif intent.status == 'succeeded'
        # The payment didn’t need any additional actions and is completed!
        # Handle post-payment fulfillment
        [200, { success: true }.to_json]
      else
        # Invalid status
        return [500, { error: 'Invalid PaymentIntent status' }.to_json]
      end
    end
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    @app.route('/ajax/confirm_payment', methods=['POST'])
    def confirm_payment():
      data = request.get_json()
      intent = None
    
      try:
        if 'payment_method_id' in data:
          # Create the PaymentIntent
          intent = stripe.PaymentIntent.create(
            payment_method = data['payment_method_id'],
            amount = 1099,
            currency = 'usd',
            confirmation_method = 'manual',
            confirm = True,
          )
        elif 'payment_intent_id' in data:
          intent = stripe.PaymentIntent.confirm(data['payment_intent_id'])
      except stripe.error.CardError as e:
        # Display error on client
        return json.dumps({'error': e.user_message}), 200
    
      return generate_payment_response(intent)
    
    def generate_payment_response(intent):
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action' and intent.next_action.type == 'use_stripe_sdk':
        # Tell the client to handle the action
        return json.dumps({
          'requires_action': True,
          'payment_intent_client_secret': intent.client_secret,
        }), 200
      elif intent.status == 'succeeded':
        # The payment didn’t need any additional actions and completed!
        # Handle post-payment fulfillment
        return json.dumps({'success': True}), 200
      else:
        # Invalid status
        return json.dumps({'error': 'Invalid PaymentIntent status'}), 500
    
    <?php
      # vendor using composer
      require_once('vendor/autoload.php');
    
      \Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
    
      header('Content-Type: application/json');
    
      # retrieve json from POST body
      $json_str = file_get_contents('php://input');
      $json_obj = json_decode($json_str);
    
      $intent = null;
      try {
        if (isset($json_obj->payment_method_id)) {
          # Create the PaymentIntent
          $intent = \Stripe\PaymentIntent::create([
            'payment_method' => $json_obj->payment_method_id,
            'amount' => 1099,
            'currency' => 'usd',
            'confirmation_method' => 'manual',
            'confirm' => true,
          ]);
        }
        if (isset($json_obj->payment_intent_id)) {
          $intent = \Stripe\PaymentIntent::retrieve(
            $json_obj->payment_intent_id
          );
          $intent->confirm();
        }
        generatePaymentResponse($intent);
      } catch (\Stripe\Exception\ApiErrorException $e) {
        # Display error on client
        echo json_encode([
          'error' => $e->getMessage()
        ]);
      }
    
      function generatePaymentResponse($intent) {
        # Note that if your API version is before 2019-02-11, 'requires_action'
        # appears as 'requires_source_action'.
        if ($intent->status == 'requires_action' &&
            $intent->next_action->type == 'use_stripe_sdk') {
          # Tell the client to handle the action
          echo json_encode([
            'requires_action' => true,
            'payment_intent_client_secret' => $intent->client_secret
          ]);
        } else if ($intent->status == 'succeeded') {
          # The payment didn’t need any additional actions and completed!
          # Handle post-payment fulfillment
          echo json_encode([
            "success" => true
          ]);
        } else {
          # Invalid status
          http_response_code(500);
          echo json_encode(['error' => 'Invalid PaymentIntent status']);
        }
      }
    ?>
    
    package com.stripe.generator;
    
    import static spark.Spark.post;
    
    import com.google.gson.Gson;
    import com.google.gson.annotations.SerializedName;
    import com.stripe.Stripe;
    import com.stripe.model.PaymentIntent;
    
    import com.stripe.param.PaymentIntentCreateParams;
    import java.util.HashMap;
    import java.util.Map;
    import spark.Request;
    import spark.Response;
    import spark.Route;
    
    public class MyApp {
    
      static class ConfirmPaymentRequest {
        @SerializedName("payment_method_id")
        String paymentMethodId;
        @SerializedName("payment_intent_id")
        String paymentIntentId;
    
        public String getPaymentMethodId() {
          return paymentMethodId;
        }
    
        public String getPaymentIntentId() {
          return paymentIntentId;
        }
      }
    
      /**
       * Your application.
       */
      public static void main(String[] args) {
    
        Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");
    
        post(new Route("/ajax/confirm_payment") {
    
          @Override
          public Object handle(Request request, Response response) {
            Gson gson = new Gson();
            ConfirmPaymentRequest confirmRequest =
                gson.fromJson(request.body(), ConfirmPaymentRequest.class);
            PaymentIntent intent;
            try {
              if (confirmRequest.getPaymentMethodId() != null) {
                PaymentIntentCreateParams createParams = PaymentIntentCreateParams.builder()
                    .setAmount(1099)
                    .setCurrency("usd")
                    .setConfirm(true)
                    .setPaymentMethod(confirmRequest.paymentMethodId)
                    .setConfirmationMethod(PaymentIntentCreateParams.ConfirmationMethod.MANUAL)
                    .build();
                intent = PaymentIntent.create(createParams);
              } else if (confirmRequest.getPaymentIntentId() != null) {
                intent = PaymentIntent.retrieve(confirmRequest.getPaymentIntentId());
                intent = intent.confirm();
              }
    
              Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              Map<String, Object> errorResponse = new HashMap<>();
              errorResponse.put("message", e.getMessage());
              return gson.toJson(errorResponse);
            }
          }
    
          private Map<String, Object> generatePaymentResponse(Response response,
                                                              PaymentIntent intent) {
            response.type("application/json");
            Map<String, Object> responseData = new HashMap<>();
            // Note that if your API version is before 2019-02-11, 'requires_action'
            // appears as 'requires_source_action'.
            if (intent.getStatus().equals("requires_action")
                && intent.getNextAction().getType().equals("use_stripe_sdk")) {
              responseData.put("requires_action", true);
              responseData.put("payment_intent_client_secret", intent.getClientSecret());
            } else if (intent.getStatus().equals("succeeded")) {
              responseData.put("success", true);
            } else {
              // invalid status
              responseData.put("Error", "Invalid status");
              response.status(500);
              return responseData;
            }
            response.status(200);
            return responseData;
          }
        });
      }
    }
    
    // Using Express
    const express = require('express');
    const app = express();
    app.use(express.json());
    
    app.post('/ajax/confirm_payment', async (request, response) => {
      try {
        let intent;
        if (request.body.payment_method_id) {
          // Create the PaymentIntent
          intent = await stripe.paymentIntents.create({
            payment_method: request.body.payment_method_id,
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true
          });
        } else if (request.body.payment_intent_id) {
          intent = await stripe.paymentIntents.confirm(
            request.body.payment_intent_id
          );
        }
        // Send the response to the client
        response.send(generate_payment_response(intent));
      } catch (e) {
        // Display error on client
        return response.send({ error: e.message });
      }
    });
    
    const generate_payment_response = (intent) => {
      // Note that if your API version is before 2019-02-11, 'requires_action'
      // appears as 'requires_source_action'.
      if (
        intent.status === 'requires_action' &&
        intent.next_action.type === 'use_stripe_sdk'
      ) {
        // Tell the client to handle the action
        return {
          requires_action: true,
          payment_intent_client_secret: intent.client_secret
        };
      } else if (intent.status === 'succeeded') {
        // The payment didn’t need any additional actions and completed!
        // Handle post-payment fulfillment
        return {
          success: true
        };
      } else {
        // Invalid status
        return {
          error: 'Invalid PaymentIntent status'
        }
      }
    };
    
    package main
    
    import (
      "encoding/json"
      "net/http"
      "os"
      "fmt"
    
      stripe "github.com/stripe/stripe-go"
      paymentintent "github.com/stripe/stripe-go/paymentintent"
    )
    
    // Structs to handle request and response JSON serializations
    type ConfirmPaymentRequest struct {
      PaymentMethodId *string `json:"payment_method_id"`
      PaymentIntentId *string `json:"payment_intent_id"`
    }
    
    type ErrorResponse struct {
      Message string `json:"error"`
    }
    
    type ConfirmPaymentResponse struct {
      RequiresAction bool `json:"requires_action"`
      PaymentIntentClientSecret *string `json:"payment_intent_client_secret"`
    }
    
    func generatePaymentResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) {
      // Note that if your API version is before 2019-02-11, PaymentIntentStatusRequiresAction
      // appears as PaymentIntentStatusRequiresSourceAction.
      if intent.Status == stripe.PaymentIntentStatusRequiresAction &&
        intent.NextAction.Type == "use_stripe_sdk" {
    
        // Tell the client to handle the action
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(ConfirmPaymentResponse{
          RequiresAction: true,
          PaymentIntentClientSecret: &intent.ClientSecret,
        })
    
      } else if intent.Status == stripe.PaymentIntentStatusSucceeded {
    
        // The payment didn’t need any additional actions and completed!
        // Handle post-payment fulfillment
        w.WriteHeader(http.StatusOK)
        fmt.Fprint(w, "Success")
    
      } else {
    
        // Invalid status
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Invalid Payment Intent status: %s", intent.Status)
    
      }
    }
    
    func main() {
      // Initialize stripe-go with Stripe secret key from environment
      stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
    
      // AJAX endpoint when `/ajax/confirm_payment` is called from client
      http.HandleFunc("/ajax/confirm_payment",
        func(w http.ResponseWriter, req *http.Request) {
          var confirmPaymentRequest ConfirmPaymentRequest
          json.NewDecoder(req.Body).Decode(&confirmPaymentRequest)
    
          var intent *stripe.PaymentIntent
          var err error
    
          if confirmPaymentRequest.PaymentMethodId != nil {
            // Create the PaymentIntent
            params := &stripe.PaymentIntentParams{
              PaymentMethod: stripe.String(*confirmPaymentRequest.PaymentMethodId),
              Amount: stripe.Int64(1099),
              Currency: stripe.String(string(stripe.CurrencyUSD)),
              ConfirmationMethod: stripe.String(string(
                stripe.PaymentIntentConfirmationMethodManual,
              )),
              Confirm: stripe.Bool(true),
            }
            intent, err = paymentintent.New(params)
          }
    
          if confirmPaymentRequest.PaymentIntentId != nil {
            intent, err = paymentintent.Confirm(
              *confirmPaymentRequest.PaymentIntentId, nil,
            )
          }
    
          if err != nil {
            if stripeErr, ok := err.(*stripe.Error); ok {
              // Display error on client
              w.WriteHeader(http.StatusOK)
              json.NewEncoder(w).Encode(ErrorResponse{Message: stripeErr.Msg})
            } else {
              w.WriteHeader(http.StatusInternalServerError)
              fmt.Fprintf(w, "Other error occurred, %v\n", err.Error())
            }
          } else {
            generatePaymentResponse(intent, w)
          }
        })
    
      http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    }
    
    using System;
    using System.Collections.Generic;
    using Microsoft.AspNetCore.Mvc;
    
    using Stripe;
    using Newtonsoft.Json;
    
    namespace Controllers
    {
        public class ConfirmPaymentRequest
        {
            [JsonProperty("payment_method_id")]
            public string PaymentMethodId { get; set; }
    
            [JsonProperty("payment_intent_id")]
            public string PaymentIntentId { get; set; }
        }
    
        // AJAX endpoint when `/ajax/confirm_payment` is called from client
        [Route("/ajax/confirm_payment")]
        public class ConfirmPaymentController : Controller
        {
            public IActionResult Index([FromBody] ConfirmPaymentRequest request)
            {
                var paymentIntentService = new PaymentIntentService();
                PaymentIntent paymentIntent = null;
    
                try
                {
                    if (request.PaymentMethodId != null)
                    {
                        // Create the PaymentIntent
                        var createOptions = new PaymentIntentCreateOptions
                        {
                            PaymentMethodId = request.PaymentMethodId,
                            Amount = 1099,
                            Currency = "usd",
                            ConfirmationMethod = "manual",
                            Confirm = true,
                        };
                        paymentIntent = paymentIntentService.Create(createOptions);
                    }
                    if (request.PaymentIntentId != null)
                    {
                        var confirmOptions = new PaymentIntentConfirmOptions{};
                        paymentIntent = paymentIntentService.Confirm(
                            request.PaymentIntentId,
                            confirmOptions
                        );
                    }
                }
                catch (StripeException e)
                {
                    return Json(new { error = e.StripeError.Message });
                }
                return generatePaymentResponse(paymentIntent);
              }
    
              private IActionResult generatePaymentResponse(PaymentIntent intent)
              {
                // Note that if your API version is before 2019-02-11, 'requires_action'
                // appears as 'requires_source_action'.
                if (intent.Status == "requires_action" &&
                    intent.NextAction.Type == "use_stripe_sdk")
                {
                    // Tell the client to handle the action
                    return Json(new
                    {
                        requires_action = true,
                        payment_intent_client_secret = intent.ClientSecret
                    });
                }
                else if (intent.Status == "succeeded")
                {
                    // The payment didn’t need any additional actions and completed!
                    // Handle post-payment fulfillment
                    return Json(new { success = true });
                }
                else
                {
                    // Invalid status
                    return StatusCode(500, new { error = "Invalid PaymentIntent status" });
                }
            }
        }
    }
    

    Step 3: Handle any next actions on the client

    If the PaymentIntent requires additional action from the customer, such as authenticating with 3D Secure, you can use stripe.handleCardAction to trigger the UI to handle the action. 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.

    While testing, use a test card number that requires authentication (e.g., 4000002760003184) to force this flow. Using a card that doesn’t require authentication (e.g., 4242424242424242) skips this part of the flow and complete at Step 2.

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

    Step 4: Confirm the PaymentIntent again on the server

    Using the same endpoint you set up in Step 2, confirm the PaymentIntent again to finalize the payment and fulfill the order.

    curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -X POST
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    post '/ajax/confirm_payment' do
      data = JSON.parse(request.body.read.to_s)
    
      begin
        if data['payment_method_id']
          # Create the PaymentIntent
          intent = Stripe::PaymentIntent.create(
            payment_method: data['payment_method_id'],
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true,
          )
        elsif data['payment_intent_id']
          intent = Stripe::PaymentIntent.confirm(data['payment_intent_id'])
        end
      rescue Stripe::CardError => e
        # Display error on client
        return [200, { error: e.message }.to_json]
      end
    
      return generate_payment_response(intent)
    end
    
    def generate_payment_response(intent)
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action' &&
          intent.next_action.type == 'use_stripe_sdk'
        # Tell the client to handle the action
        [
          200,
          {
            requires_action: true,
            payment_intent_client_secret: intent.client_secret
          }.to_json
        ]
      elsif intent.status == 'succeeded'
        # The payment didn’t need any additional actions and is completed!
        # Handle post-payment fulfillment
        [200, { success: true }.to_json]
      else
        # Invalid status
        return [500, { error: 'Invalid PaymentIntent status' }.to_json]
      end
    end
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    @app.route('/ajax/confirm_payment', methods=['POST'])
    def confirm_payment():
      data = request.get_json()
      intent = None
    
      try:
        if 'payment_method_id' in data:
          # Create the PaymentIntent
          intent = stripe.PaymentIntent.create(
            payment_method = data['payment_method_id'],
            amount = 1099,
            currency = 'usd',
            confirmation_method = 'manual',
            confirm = True,
          )
        elif 'payment_intent_id' in data:
          intent = stripe.PaymentIntent.confirm(data['payment_intent_id'])
      except stripe.error.CardError as e:
        # Display error on client
        return json.dumps({'error': e.user_message}), 200
    
      return generate_payment_response(intent)
    
    def generate_payment_response(intent):
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action' and intent.next_action.type == 'use_stripe_sdk':
        # Tell the client to handle the action
        return json.dumps({
          'requires_action': True,
          'payment_intent_client_secret': intent.client_secret,
        }), 200
      elif intent.status == 'succeeded':
        # The payment didn’t need any additional actions and completed!
        # Handle post-payment fulfillment
        return json.dumps({'success': True}), 200
      else:
        # Invalid status
        return json.dumps({'error': 'Invalid PaymentIntent status'}), 500
    
    <?php
      # vendor using composer
      require_once('vendor/autoload.php');
    
      \Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
    
      header('Content-Type: application/json');
    
      # retrieve json from POST body
      $json_str = file_get_contents('php://input');
      $json_obj = json_decode($json_str);
    
      $intent = null;
      try {
        if (isset($json_obj->payment_method_id)) {
          # Create the PaymentIntent
          $intent = \Stripe\PaymentIntent::create([
            'payment_method' => $json_obj->payment_method_id,
            'amount' => 1099,
            'currency' => 'usd',
            'confirmation_method' => 'manual',
            'confirm' => true,
          ]);
        }
        if (isset($json_obj->payment_intent_id)) {
          $intent = \Stripe\PaymentIntent::retrieve(
            $json_obj->payment_intent_id
          );
          $intent->confirm();
        }
        generatePaymentResponse($intent);
      } catch (\Stripe\Exception\ApiErrorException $e) {
        # Display error on client
        echo json_encode([
          'error' => $e->getMessage()
        ]);
      }
    
      function generatePaymentResponse($intent) {
        # Note that if your API version is before 2019-02-11, 'requires_action'
        # appears as 'requires_source_action'.
        if ($intent->status == 'requires_action' &&
            $intent->next_action->type == 'use_stripe_sdk') {
          # Tell the client to handle the action
          echo json_encode([
            'requires_action' => true,
            'payment_intent_client_secret' => $intent->client_secret
          ]);
        } else if ($intent->status == 'succeeded') {
          # The payment didn’t need any additional actions and completed!
          # Handle post-payment fulfillment
          echo json_encode([
            "success" => true
          ]);
        } else {
          # Invalid status
          http_response_code(500);
          echo json_encode(['error' => 'Invalid PaymentIntent status']);
        }
      }
    ?>
    
    package com.stripe.generator;
    
    import static spark.Spark.post;
    
    import com.google.gson.Gson;
    import com.google.gson.annotations.SerializedName;
    import com.stripe.Stripe;
    import com.stripe.model.PaymentIntent;
    
    import com.stripe.param.PaymentIntentCreateParams;
    import java.util.HashMap;
    import java.util.Map;
    import spark.Request;
    import spark.Response;
    import spark.Route;
    
    public class MyApp {
    
      static class ConfirmPaymentRequest {
        @SerializedName("payment_method_id")
        String paymentMethodId;
        @SerializedName("payment_intent_id")
        String paymentIntentId;
    
        public String getPaymentMethodId() {
          return paymentMethodId;
        }
    
        public String getPaymentIntentId() {
          return paymentIntentId;
        }
      }
    
      /**
       * Your application.
       */
      public static void main(String[] args) {
    
        Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");
    
        post(new Route("/ajax/confirm_payment") {
    
          @Override
          public Object handle(Request request, Response response) {
            Gson gson = new Gson();
            ConfirmPaymentRequest confirmRequest =
                gson.fromJson(request.body(), ConfirmPaymentRequest.class);
            PaymentIntent intent;
            try {
              if (confirmRequest.getPaymentMethodId() != null) {
                PaymentIntentCreateParams createParams = PaymentIntentCreateParams.builder()
                    .setAmount(1099)
                    .setCurrency("usd")
                    .setConfirm(true)
                    .setPaymentMethod(confirmRequest.paymentMethodId)
                    .setConfirmationMethod(PaymentIntentCreateParams.ConfirmationMethod.MANUAL)
                    .build();
                intent = PaymentIntent.create(createParams);
              } else if (confirmRequest.getPaymentIntentId() != null) {
                intent = PaymentIntent.retrieve(confirmRequest.getPaymentIntentId());
                intent = intent.confirm();
              }
    
              Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              Map<String, Object> errorResponse = new HashMap<>();
              errorResponse.put("message", e.getMessage());
              return gson.toJson(errorResponse);
            }
          }
    
          private Map<String, Object> generatePaymentResponse(Response response,
                                                              PaymentIntent intent) {
            response.type("application/json");
            Map<String, Object> responseData = new HashMap<>();
            // Note that if your API version is before 2019-02-11, 'requires_action'
            // appears as 'requires_source_action'.
            if (intent.getStatus().equals("requires_action")
                && intent.getNextAction().getType().equals("use_stripe_sdk")) {
              responseData.put("requires_action", true);
              responseData.put("payment_intent_client_secret", intent.getClientSecret());
            } else if (intent.getStatus().equals("succeeded")) {
              responseData.put("success", true);
            } else {
              // invalid status
              responseData.put("Error", "Invalid status");
              response.status(500);
              return responseData;
            }
            response.status(200);
            return responseData;
          }
        });
      }
    }
    
    // Using Express
    const express = require('express');
    const app = express();
    app.use(express.json());
    
    app.post('/ajax/confirm_payment', async (request, response) => {
      try {
        let intent;
        if (request.body.payment_method_id) {
          // Create the PaymentIntent
          intent = await stripe.paymentIntents.create({
            payment_method: request.body.payment_method_id,
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true
          });
        } else if (request.body.payment_intent_id) {
          intent = await stripe.paymentIntents.confirm(
            request.body.payment_intent_id
          );
        }
        // Send the response to the client
        response.send(generate_payment_response(intent));
      } catch (e) {
        // Display error on client
        return response.send({ error: e.message });
      }
    });
    
    const generate_payment_response = (intent) => {
      // Note that if your API version is before 2019-02-11, 'requires_action'
      // appears as 'requires_source_action'.
      if (
        intent.status === 'requires_action' &&
        intent.next_action.type === 'use_stripe_sdk'
      ) {
        // Tell the client to handle the action
        return {
          requires_action: true,
          payment_intent_client_secret: intent.client_secret
        };
      } else if (intent.status === 'succeeded') {
        // The payment didn’t need any additional actions and completed!
        // Handle post-payment fulfillment
        return {
          success: true
        };
      } else {
        // Invalid status
        return {
          error: 'Invalid PaymentIntent status'
        }
      }
    };
    
    package main
    
    import (
      "encoding/json"
      "net/http"
      "os"
      "fmt"
    
      stripe "github.com/stripe/stripe-go"
      paymentintent "github.com/stripe/stripe-go/paymentintent"
    )
    
    // Structs to handle request and response JSON serializations
    type ConfirmPaymentRequest struct {
      PaymentMethodId *string `json:"payment_method_id"`
      PaymentIntentId *string `json:"payment_intent_id"`
    }
    
    type ErrorResponse struct {
      Message string `json:"error"`
    }
    
    type ConfirmPaymentResponse struct {
      RequiresAction bool `json:"requires_action"`
      PaymentIntentClientSecret *string `json:"payment_intent_client_secret"`
    }
    
    func generatePaymentResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) {
      // Note that if your API version is before 2019-02-11, PaymentIntentStatusRequiresAction
      // appears as PaymentIntentStatusRequiresSourceAction.
      if intent.Status == stripe.PaymentIntentStatusRequiresAction &&
        intent.NextAction.Type == "use_stripe_sdk" {
    
        // Tell the client to handle the action
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(ConfirmPaymentResponse{
          RequiresAction: true,
          PaymentIntentClientSecret: &intent.ClientSecret,
        })
    
      } else if intent.Status == stripe.PaymentIntentStatusSucceeded {
    
        // The payment didn’t need any additional actions and completed!
        // Handle post-payment fulfillment
        w.WriteHeader(http.StatusOK)
        fmt.Fprint(w, "Success")
    
      } else {
    
        // Invalid status
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Invalid Payment Intent status: %s", intent.Status)
    
      }
    }
    
    func main() {
      // Initialize stripe-go with Stripe secret key from environment
      stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
    
      // AJAX endpoint when `/ajax/confirm_payment` is called from client
      http.HandleFunc("/ajax/confirm_payment",
        func(w http.ResponseWriter, req *http.Request) {
          var confirmPaymentRequest ConfirmPaymentRequest
          json.NewDecoder(req.Body).Decode(&confirmPaymentRequest)
    
          var intent *stripe.PaymentIntent
          var err error
    
          if confirmPaymentRequest.PaymentMethodId != nil {
            // Create the PaymentIntent
            params := &stripe.PaymentIntentParams{
              PaymentMethod: stripe.String(*confirmPaymentRequest.PaymentMethodId),
              Amount: stripe.Int64(1099),
              Currency: stripe.String(string(stripe.CurrencyUSD)),
              ConfirmationMethod: stripe.String(string(
                stripe.PaymentIntentConfirmationMethodManual,
              )),
              Confirm: stripe.Bool(true),
            }
            intent, err = paymentintent.New(params)
          }
    
          if confirmPaymentRequest.PaymentIntentId != nil {
            intent, err = paymentintent.Confirm(
              *confirmPaymentRequest.PaymentIntentId, nil,
            )
          }
    
          if err != nil {
            if stripeErr, ok := err.(*stripe.Error); ok {
              // Display error on client
              w.WriteHeader(http.StatusOK)
              json.NewEncoder(w).Encode(ErrorResponse{Message: stripeErr.Msg})
            } else {
              w.WriteHeader(http.StatusInternalServerError)
              fmt.Fprintf(w, "Other error occurred, %v\n", err.Error())
            }
          } else {
            generatePaymentResponse(intent, w)
          }
        })
    
      http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    }
    
    using System;
    using System.Collections.Generic;
    using Microsoft.AspNetCore.Mvc;
    
    using Stripe;
    using Newtonsoft.Json;
    
    namespace Controllers
    {
        public class ConfirmPaymentRequest
        {
            [JsonProperty("payment_method_id")]
            public string PaymentMethodId { get; set; }
    
            [JsonProperty("payment_intent_id")]
            public string PaymentIntentId { get; set; }
        }
    
        // AJAX endpoint when `/ajax/confirm_payment` is called from client
        [Route("/ajax/confirm_payment")]
        public class ConfirmPaymentController : Controller
        {
            public IActionResult Index([FromBody] ConfirmPaymentRequest request)
            {
                var paymentIntentService = new PaymentIntentService();
                PaymentIntent paymentIntent = null;
    
                try
                {
                    if (request.PaymentMethodId != null)
                    {
                        // Create the PaymentIntent
                        var createOptions = new PaymentIntentCreateOptions
                        {
                            PaymentMethodId = request.PaymentMethodId,
                            Amount = 1099,
                            Currency = "usd",
                            ConfirmationMethod = "manual",
                            Confirm = true,
                        };
                        paymentIntent = paymentIntentService.Create(createOptions);
                    }
                    if (request.PaymentIntentId != null)
                    {
                        var confirmOptions = new PaymentIntentConfirmOptions{};
                        paymentIntent = paymentIntentService.Confirm(
                            request.PaymentIntentId,
                            confirmOptions
                        );
                    }
                }
                catch (StripeException e)
                {
                    return Json(new { error = e.StripeError.Message });
                }
                return generatePaymentResponse(paymentIntent);
              }
    
              private IActionResult generatePaymentResponse(PaymentIntent intent)
              {
                // Note that if your API version is before 2019-02-11, 'requires_action'
                // appears as 'requires_source_action'.
                if (intent.Status == "requires_action" &&
                    intent.NextAction.Type == "use_stripe_sdk")
                {
                    // Tell the client to handle the action
                    return Json(new
                    {
                        requires_action = true,
                        payment_intent_client_secret = intent.ClientSecret
                    });
                }
                else if (intent.Status == "succeeded")
                {
                    // The payment didn’t need any additional actions and completed!
                    // Handle post-payment fulfillment
                    return Json(new { success = true });
                }
                else
                {
                    // Invalid status
                    return StatusCode(500, new { error = "Invalid PaymentIntent status" });
                }
            }
        }
    }
    

    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.

    Viewport meta tag requirements

    In order to provide a great user experience for 3D Secure on all devices, you should set your page’s viewport width to device-width with the the viewport meta tag. There are several other viewport settings, and you can configure those based on your needs. Just make sure you include width=device-width somewhere in your configuration.

    For instance the following will work with Elements:

    <meta name="viewport" content="width=device-width, initial-scale=1" />

    If you already have this tag on your page and it includes width=device-width, then you are all set.

    Next steps

    Now you have a complete Payment Intents API integration suitable for accepting card payments. To learn more, continue reading:

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    Questions?

    We're always happy to help with code or other questions you might have. Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    On this page