Finalize payments on the server Server-side confirmation

    Learn how to confirm a payment on your server and handle card authentication requests.

    Overview

    For a wider range of support and future proofing, we strongly recommend our standard integration for asynchronous payments.

    This guide lets you wait for the returned response from the client and finalize a payment on the server, without using webhooks or processing offline events. While it may seem simpler, this integration is difficult to scale as your business grows.

    Limitations
    • Only supports cards: You’ll have to write more code to support ACH and popular regional payment methods separately.
    • Double-charge risk: By synchronously creating a new PaymentIntent each time your customer attempts to pay, you risk accidently double-charging your customer. Be sure to follow best practices.
    • Extra trip to client: If your customer has a card with 3D Secure or is subject to regulations like Strong Customer Authentication, this integration requires an extra trip to the client to authenticate.

    If you don’t mind these limitations, feel free to integrate. Otherwise, use the standard integration.

    Demo

    1 Set up Stripe

    First, you need a Stripe account. Register now.

    Use our official libraries for access to the Stripe API from your application:

    Available as a gem:

    sudo gem install stripe

    If you use bundler, you can use this line:

    gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'

    Available through pip:

    pip install --upgrade stripe

    Alternatively, you can also use easy_install:

    easy_install --upgrade stripe

    The PHP library can be installed via Composer:

    composer require stripe/stripe-php

    Alternatively, you can download the source directly.

    For Gradle, add the following dependency to your build.gradle:

    implementation "com.stripe:stripe-java:{VERSION}"
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    For Maven, add the following dependency to your POM:

    <dependency>
      <groupId>com.stripe</groupId>
      <artifactId>stripe-java</artifactId>
      <version>{VERSION}</version>
    </dependency>
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    In other environments, manually install the following JARs:

    Install via npm:

    npm install stripe

    Install via go:

    go get github.com/stripe/stripe-go

    Then import the package:

    import (
      "github.com/stripe/stripe-go"
    )

    Install via dotnet:

    dotnet add package Stripe.net
    dotnet restore

    Or using NuGet:

    PM> Install-Package Stripe.net

    2 Collect card details Client-side

    Collect card information on the client with Stripe.js and Stripe Elements. Elements is a set of prebuilt UI components for collecting and validating card number, ZIP code, and expiration date.

    Load Stripe.js

    Include the Stripe.js script in the head of every page on your site. Elements is automatically available as a feature of Stripe.js.

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

    Including the script on every page of your site lets you take advantage of Stripe’s advanced fraud functionality and ability to detect anomalous browsing behavior.

    Build the payment form

    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 as an iframe. 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();
    // Set up Stripe.js and Elements to use in checkout form
    var style = {
      base: {
        color: "#32325d",
      }
    };
    
    var cardElement = elements.create('card', {style: style});
    cardElement.mount('#card-element');
    const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    const elements = stripe.elements();
    
    // Set up Stripe.js and Elements to use in checkout form
    const style = {
      base: {
        color: "#32325d",
      }
    };
    
    const cardElement = elements.create('card', {style: style});
    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 3)
          fetch('/pay', {
            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 3)
        const response = await fetch('/pay', {
          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);
      }
    });
    
    

    3 Create a PaymentIntent Server-side

    Set up an endpoint on your server to receive the request. This endpoint will also be used in Step 5 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 `/pay` is called from client
    post '/pay' 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_response(intent)
    end
    
    def generate_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 `/pay` is called from client
    @app.route('/pay', methods=['POST'])
    def pay():
      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_response(intent)
    
    def generate_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();
        }
        generateResponse($intent);
      } catch (\Stripe\Exception\ApiErrorException $e) {
        # Display error on client
        echo json_encode([
          'error' => $e->getMessage()
        ]);
      }
    
      function generateResponse($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("/pay") {
    
          @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 = generateResponse(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> generateResponse(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('/pay', 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(generateResponse(intent));
      } catch (e) {
        // Display error on client
        return response.send({ error: e.message });
      }
    });
    
    const generateResponse = (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 `/pay` is called from client
      http.HandleFunc("/pay",
        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 `/pay` is called from client
        [Route("/pay")]
        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
                        {
                            PaymentMethod = 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" });
                }
            }
        }
    }
    

    If you want to save the card to reuse later, create a Customer to store the PaymentMethod and pass the following additional parameters when creating the PaymentIntent:

    • setup_future_usage. Set to off_session to tell Stripe that you plan to reuse this PaymentMethod for off-session payments when your customer is not present.
    • customer. Set to the ID of the Customer.
    • save_payment_method. Set to true to save the PaymentMethod to the Customer.

    See the code sample on saving cards after a payment for more details.

    4 Handle any next actions Client-side

    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 completes at step 3.

    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('/pay', {
              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('/pay', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ payment_intent_id: paymentIntent.id })
          });
          handleServerResponse(await serverResponse.json());
        }
      } else {
        // Show success message
      }
    }
    

    5 Confirm the PaymentIntent again Server-side

    Using the same endpoint you set up in step 2, confirm the PaymentIntent again to finalize the payment and fulfill the order. The payment attempt fails and transitions back to requires_payment_method if it is not confirmed again within one hour.

    curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -X POST
    
    # AJAX endpoint when `/pay` is called from client
    post '/pay' 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_response(intent)
    end
    
    def generate_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 `/pay` is called from client
    @app.route('/pay', methods=['POST'])
    def pay():
      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_response(intent)
    
    def generate_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();
        }
        generateResponse($intent);
      } catch (\Stripe\Exception\ApiErrorException $e) {
        # Display error on client
        echo json_encode([
          'error' => $e->getMessage()
        ]);
      }
    
      function generateResponse($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("/pay") {
    
          @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 = generateResponse(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> generateResponse(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('/pay', 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(generateResponse(intent));
      } catch (e) {
        // Display error on client
        return response.send({ error: e.message });
      }
    });
    
    const generateResponse = (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 `/pay` is called from client
      http.HandleFunc("/pay",
        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 `/pay` is called from client
        [Route("/pay")]
        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
                        {
                            PaymentMethod = 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" });
                }
            }
        }
    }
    

    6 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000002500003155 Requires authentication. Stripe will trigger a modal asking for the customer to authenticate.
    4000000000009995 Always fails with a decline code of insufficient_funds.

    For the full list of test cards see our guide on testing.

    Overview

    For a wider range of support and future proofing, we strongly recommend our standard integration for asynchronous payments.

    This guide lets you use a single client-to-server flow to take payments, without using webhooks or processing offline events. While it may seem simpler, this integration will be difficult to scale as your business grows. Synchronous payment flows have several limitations:

    • Only supports cards: You’ll have to write more code to support ACH and popular regional payment methods separately.
    • Double-charge risk: By synchronously creating a new PaymentIntent each time your customer attempts to pay, you risk accidently double-charging your customer. Be sure to follow best practices.
    • Manual authentication handling: If your customer has a card with 3D Secure or is subject to regulations like Strong Customer Authentication, you’ll have to make extra trips to the client.

    If you don’t mind these limitations, feel free to integrate. Otherwise, use the standard integration.

    1 Set up Stripe Client-side Server-side

    First, you need a Stripe account. Register now.

    Server-side

    This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

    Available as a gem:

    sudo gem install stripe

    If you use bundler, you can use this line:

    gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'

    Available through pip:

    pip install --upgrade stripe

    Alternatively, you can also use easy_install:

    easy_install --upgrade stripe

    The PHP library can be installed via Composer:

    composer require stripe/stripe-php

    Alternatively, you can download the source directly.

    For Gradle, add the following dependency to your build.gradle:

    implementation "com.stripe:stripe-java:{VERSION}"
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    For Maven, add the following dependency to your POM:

    <dependency>
      <groupId>com.stripe</groupId>
      <artifactId>stripe-java</artifactId>
      <version>{VERSION}</version>
    </dependency>
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    In other environments, manually install the following JARs:

    Install via npm:

    npm install stripe

    Install via go:

    go get github.com/stripe/stripe-go

    Then import the package:

    import (
      "github.com/stripe/stripe-go"
    )

    Install via dotnet:

    dotnet add package Stripe.net
    dotnet restore

    Or using NuGet:

    PM> Install-Package Stripe.net

    Client-side

    The iOS SDK is open source, fully documented, and compatible with apps supporting iOS 9 or above.

    1. If you haven't already, install the latest version of CocoaPods.
    2. If you don't have an existing Podfile, run the following command to create one:
      pod init
    3. Add this line to your Podfile:
      pod 'Stripe'
    4. Run the following command:
      pod install
    5. Don't forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.
    6. In the future, to update to the latest version of the SDK, just run:
      pod update Stripe
    1. If you haven't already, install the latest version of Carthage.
    2. Add this line to your Cartfile:
      github "stripe/stripe-ios"
    3. Follow the Carthage installation instructions.
    4. In the future, to update to the latest version of the SDK, run the following command:
      carthage update stripe-ios --platform ios
    1. Head to our GitHub releases page and download and unzip Stripe.framework.zip.
    2. Drag Stripe.framework to the "Embedded Binaries" section of your Xcode project's "General" settings. Make sure to select "Copy items if needed".
    3. Head to the "Build Phases" section of your Xcode project settings, and create a new "Run Script Build Phase". Paste the following snippet into the text field:
      bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Stripe.framework/integrate-dynamic-framework.sh"
    4. In the future, to update to the latest version of our SDK, just repeat steps 1 and 2.

    When your app starts, configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API.

    import UIKit
    import Stripe
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            Stripe.setDefaultPublishableKey("pk_test_TYooMQauvdEDq54NiTphI7jx")
            // do any other necessary launch configuration
            return true
        }
    }
    #import "AppDelegate.h"
    #import <Stripe/Stripe.h>
    
    @implementation AppDelegate
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [Stripe setDefaultPublishableKey:@"pk_test_TYooMQauvdEDq54NiTphI7jx"];
        // do any other necessary launch configuration
        return YES;
    }
    @end

    2 Create your checkout screen Client-side

    Securely collect card information on the client with STPPaymentCardTextField, a drop-in UI component provided by the SDK.

    STPPaymentCardTextField performs on-the-fly validation and formatting.

    Create an instance of the card component and a “Pay” button with the following code:

    import UIKit
    import Stripe
    
    class CheckoutViewController: UIViewController {
    
        lazy var cardTextField: STPPaymentCardTextField = {
            let cardTextField = STPPaymentCardTextField()
            return cardTextField
        }()
        lazy var payButton: UIButton = {
            let button = UIButton(type: .custom)
            button.layer.cornerRadius = 5
            button.backgroundColor = .systemBlue
            button.titleLabel?.font = UIFont.systemFont(ofSize: 22)
            button.setTitle("Pay", for: .normal)
            button.addTarget(self, action: #selector(pay), for: .touchUpInside)
            return button
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .white
            let stackView = UIStackView(arrangedSubviews: [cardTextField, payButton])
            stackView.axis = .vertical
            stackView.spacing = 20
            stackView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(stackView)
            NSLayoutConstraint.activate([
                stackView.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: 2),
                view.rightAnchor.constraint(equalToSystemSpacingAfter: stackView.rightAnchor, multiplier: 2),
                stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.topAnchor, multiplier: 2),
            ])
        }
    
        @objc
        func pay() {
            // ...
        }
    }
    
    #import "CheckoutViewController.h"
    #import <Stripe/Stripe.h>
    
    @interface CheckoutViewController ()
    
    @property (nonatomic, weak) STPPaymentCardTextField *cardTextField;
    @property (nonatomic, weak) UIButton *payButton;
    
    @end
    
    @implementation CheckoutViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
    
        STPPaymentCardTextField *cardTextField = [[STPPaymentCardTextField alloc] init];
        self.cardTextField = cardTextField;
    
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.layer.cornerRadius = 5;
        button.backgroundColor = [UIColor systemBlueColor];
        button.titleLabel.font = [UIFont systemFontOfSize:22];
        [button setTitle:@"Pay" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(pay) forControlEvents:UIControlEventTouchUpInside];
        self.payButton = button;
    
        UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[cardTextField, button]];
        stackView.axis = UILayoutConstraintAxisVertical;
        stackView.translatesAutoresizingMaskIntoConstraints = NO;
        stackView.spacing = 20;
        [self.view addSubview:stackView];
    
        [NSLayoutConstraint activateConstraints:@[
            [stackView.leftAnchor constraintEqualToSystemSpacingAfterAnchor:self.view.leftAnchor multiplier:2],
            [self.view.rightAnchor constraintEqualToSystemSpacingAfterAnchor:stackView.rightAnchor multiplier:2],
            [stackView.topAnchor constraintEqualToSystemSpacingBelowAnchor:self.view.topAnchor multiplier:2],
        ]];
    }
    
    - (void)pay {
        // ...
    }
    
    @end
    

    Run your app, and make sure your checkout screen shows the card component and pay button.

    3 Collect card details Client-side

    When your customer is ready to check out, create a PaymentMethod with the details collected by the card element.

    
    class CheckoutViewController: UIViewController {
    
        // ...
    
        @objc
        func pay() {
            // Collect card details on the client
            let cardParams = cardTextField.cardParams
            let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
            STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams) { [weak self] paymentMethod, error in
                // Create PaymentMethod failed
                if let createError = error {
                    self?.displayAlert(title: "Payment failed", message: createError.localizedDescription)
                }
                if let paymentMethodId = paymentMethod?.stripeId {
                    self?.pay(withPaymentMethod: paymentMethodId)
                }
            }
        }
    
        func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
            // ...continued in the next step
        }
    
        // ...
    }
    
    
    @implementation CheckoutViewController
    
    // ...
    
    - (void)pay {
        // Collect card details on the client
        STPPaymentMethodCardParams *cardParams = self.cardTextField.cardParams;
        STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:nil metadata:nil];
        [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:^(STPPaymentMethod *paymentMethod, NSError *createError) {
            // Create PaymentMethod failed
            if (createError != nil) {
                [self displayAlertWithTitle:@"Payment failed" message:createError.localizedDescription ?: @"" restartDemo:NO];
            }
            else if (paymentMethod != nil) {
                // Create a PaymentIntent on the server with a PaymentMethod
                NSLog(@"Created PaymentMethod");
                [self payWithPaymentMethod:paymentMethod.stripeId orPaymentIntent:nil];
            }
        }];
    }
    
    // ...
    
    @end
    

    4 Create a PaymentIntent Client-side Server-side

    Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.

    On the server

    Add an endpoint that creates the PaymentIntent with the following parameters:

    If you want to save the card to reuse later, create a Customer to store the PaymentMethod and pass the following additional parameters when creating the PaymentIntent:

    • setup_future_usage. Set to off_session to tell Stripe that you plan to reuse this PaymentMethod for off-session payments when your customer is not present.
    • customer. Set to the ID of the Customer.
    • save_payment_method. Set to true to save the PaymentMethod to the Customer.

    Once the PaymentIntent is created, return the following to the client:

    • the PaymentIntent’s client secret
    • requiresAction: true if the PaymentIntent status is requires_action

    On the client

    Request a PaymentIntent from your server. This example passes the server a list of items to determine the price. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    let BackendUrl = "http://127.0.0.1:4242/"
    
    class CheckoutViewController: UIViewController {
    
        // ...
    
        func displayAlert(title: String, message: String, restartDemo: Bool = false) {
            // ...omitted for brevity
        }
    
        func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
            // Create a PaymentIntent on the server
            let url = URL(string: BackendUrl + "pay")!
            var json: [String: Any] = [:]
            if let paymentMethodId = paymentMethodId {
    See all 61 lines json = [ "useStripeSdk": true, "paymentMethodId": paymentMethodId, "currency": "usd", "items": [ "id": "photo_subscription" ] ] } else if let paymentIntentId = paymentIntentId { json = [ "paymentIntentId": paymentIntentId, ] } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try? JSONSerialization.data(withJSONObject: json) let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, requestError) in guard let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] else { self?.displayAlert(title: "Payment failed", message: requestError?.localizedDescription ?? "") return } let payError = json["error"] as? String let clientSecret = json["clientSecret"] as? String let requiresAction = json["requiresAction"] as? Bool // Payment failed if let payError = payError { self?.displayAlert(title: "Payment failed", message: payError) } // Payment succeeded, no additional action required else if clientSecret != nil && (requiresAction == nil || requiresAction == false) { self?.displayAlert(title: "Payment succeeded", message: clientSecret ?? "", restartDemo: true) } // Payment requires additional action else if clientSecret != nil && requiresAction == true && self != nil { // ...continued in the next step } }) task.resume() } }
    @implementation CheckoutViewController
    
    // ...
    
    - (void)displayAlertWithTitle:(NSString *)title message:(NSString *)message restartDemo:(BOOL)restartDemo {
        // ...omitted for brevity
    }
    
    - (void)payWithPaymentMethod:(NSString *)paymentMethodId orPaymentIntent:(NSString *)paymentIntentId {
        NSDictionary *json = @{};
        if (paymentMethodId != nil) {
            json = @{
                @"useStripeSdk": @YES,
                @"paymentMethodId": paymentMethodId,
                @"currency": @"usd",
                @"items": @[
                        @{@"id": @"photo_subscription"}
                ]
            };
        }
        else {
            json = @{
                @"paymentIntentId": paymentIntentId,
            };
        }
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@pay", BackendUrl]];
        NSData *body = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil];
        NSMutableURLRequest *request = [[NSURLRequest requestWithURL:url] mutableCopy];
        [request setHTTPMethod:@"POST"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:body];
        NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *requestError) {
            NSError *error = requestError;
            NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            // Request failed
            if (error != nil || httpResponse.statusCode != 200) {
                [self displayAlertWithTitle:@"Payment failed" message:error.localizedDescription ?: @"" restartDemo:NO];
            }
            else {
                NSNumber *requiresAction = json[@"requiresAction"];
                NSString *clientSecret = json[@"clientSecret"];
                NSString *payError = json[@"error"];
                // Payment failed
                if (payError != nil) {
                    [self displayAlertWithTitle:@"Payment failed" message:payError restartDemo:NO];
                }
                // Payment succeeded
                else if (clientSecret != nil && (requiresAction == nil || [requiresAction isEqualToNumber:@NO])) {
                    [self displayAlertWithTitle:@"Payment succeeded" message:clientSecret restartDemo:YES];
                }
                // Payment requires additional actions
                else if (clientSecret != nil && [requiresAction isEqualToNumber:@YES]) {
                    // ...continued in the next step
                }
            }
        }];
        [task resume];
    }
    
    // ...
    
    @end
    

    5 Handle any next actions Client-side

    If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. On the client, pass the PaymentIntent ID to STPPaymentHandler handleNextActionForPayment. STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks the customer through authentication. See Supporting 3D Secure Authentication on iOS to learn more.

    After handling a required action on the client, the status of the PaymentIntent is requires_confirmation. You must send the PaymentIntent ID to your backend and confirm it to finalize the payment. The payment attempt fails and transitions back to requires_payment_method if it is not confirmed again within one hour. This step enables your integration to synchronously fulfill the order on your backend and return the fulfillment result to your client.

    class CheckoutViewController: UIViewController {
    
        // ...
    
        // Create or confirm a PaymentIntent on the server
        func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
            // ...
                // Payment requires additional action
                else if clientSecret != nil && requiresAction == true && self != nil {
                    let paymentHandler = STPPaymentHandler.shared()
                    paymentHandler.handleNextAction(forPayment: clientSecret!, authenticationContext: self!, returnURL: nil) { status, paymentIntent, handleActionError in
                        switch (status) {
                        case .failed:
                            self?.displayAlert(title: "Payment failed", message: handleActionError?.localizedDescription ?? "")
                            break
    See all 42 lines case .canceled: self?.displayAlert(title: "Payment canceled", message: handleActionError?.localizedDescription ?? "") break case .succeeded: if let paymentIntent = paymentIntent, paymentIntent.status == STPPaymentIntentStatus.requiresConfirmation { print("Re-confirming PaymentIntent after handling action") self?.pay(withPaymentIntent: paymentIntent.stripeId) } else { self?.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true) } break @unknown default: fatalError() break } } } // ... } } extension CheckoutViewController: STPAuthenticationContext { func authenticationPresentingViewController() -> UIViewController { return self } }
    @implementation CheckoutViewController <STPAuthenticationContext>
    
    // ...
    
    - (void)payWithPaymentMethod:(NSString *)paymentMethodId orPaymentIntent:(NSString *)paymentIntentId {
        // ...
                // Payment requires additional actions
                else if (clientSecret != nil && [requiresAction isEqualToNumber:@YES]) {
                    STPPaymentHandler *paymentHandler = [STPPaymentHandler sharedHandler];
                    [paymentHandler handleNextActionForPayment:clientSecret withAuthenticationContext:self returnURL:nil completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent *paymentIntent, NSError *handleActionError) {
                            switch (status) {
                                case STPPaymentHandlerActionStatusFailed: {
                                    [self displayAlertWithTitle:@"Payment failed" message:handleActionError.localizedDescription ?: @"" restartDemo:NO];
                                    break;
                                }
                                case STPPaymentHandlerActionStatusCanceled: {
                                    [self displayAlertWithTitle:@"Payment canceled" message:handleActionError.localizedDescription ?: @"" restartDemo:NO];
                                    break;
                                }
                                case STPPaymentHandlerActionStatusSucceeded: {
                                    // After handling a required action on the client, the status of the PaymentIntent is
                                    // requires_confirmation. You must send the PaymentIntent ID to your backend
                                    // and confirm it to finalize the payment. This step enables your integration to
                                    // synchronously fulfill the order on your backend and return the fulfillment result
                                    // to your client.
                                    if (paymentIntent.status == STPPaymentIntentStatusRequiresConfirmation) {
                                        NSLog(@"Re-confirming PaymentIntent after handling action");
                                        [self payWithPaymentMethod:nil orPaymentIntent:paymentIntent.stripeId];
                                    }
                                    else {
                                        [self displayAlertWithTitle:@"Payment succeeded" message:paymentIntent.description restartDemo:YES];
                                    }
                                    break;
                                }
                                default:
                                    break;
                            }
                    }];
                }
        // ...
    }
    
    # pragma mark STPAuthenticationContext
    - (UIViewController *)authenticationPresentingViewController {
        return self;
    }
    
    @end
    

    6 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000002500003155 Requires authentication. Stripe will trigger a modal asking for the customer to authenticate.
    4000000000009995 Always fails with a decline code of insufficient_funds.

    For the full list of test cards see our guide on testing.

    Next steps

    Now that you have a working integration, test your payment flows using the test card numbers and PaymentMethods:

    Overview

    For a wider range of support and future proofing, we strongly recommend our standard integration for asynchronous payments.

    This guide lets you use a single client-to-server flow to take payments, without using webhooks or processing offline events. While it may seem simpler, this integration will be difficult to scale as your business grows. Synchronous payment flows have several limitations:

    • Only supports cards: You’ll have to write more code to support ACH and popular regional payment methods separately.
    • Double-charge risk: By synchronously creating a new PaymentIntent each time your customer attempts to pay, you risk accidently double-charging your customer. Be sure to follow best practices.
    • Manual authentication handling: If your customer has a card with 3D Secure or is subject to regulations like Strong Customer Authentication, you’ll have to make extra trips to the client.

    If you don’t mind these limitations, feel free to integrate. Otherwise, use the standard integration.

    1 Set up Stripe Client-side Server-side

    First, you need a Stripe account. Register now.

    Server-side

    This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

    Available as a gem:

    sudo gem install stripe

    If you use bundler, you can use this line:

    gem 'stripe', :git => 'https://github.com/stripe/stripe-ruby'

    Available through pip:

    pip install --upgrade stripe

    Alternatively, you can also use easy_install:

    easy_install --upgrade stripe

    The PHP library can be installed via Composer:

    composer require stripe/stripe-php

    Alternatively, you can download the source directly.

    For Gradle, add the following dependency to your build.gradle:

    implementation "com.stripe:stripe-java:{VERSION}"
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    For Maven, add the following dependency to your POM:

    <dependency>
      <groupId>com.stripe</groupId>
      <artifactId>stripe-java</artifactId>
      <version>{VERSION}</version>
    </dependency>
    (Replace {VERSION} with the actual version number you want to use. You can find the most recent version on Maven Repository or GitHub.)

    In other environments, manually install the following JARs:

    Install via npm:

    npm install stripe

    Install via go:

    go get github.com/stripe/stripe-go

    Then import the package:

    import (
      "github.com/stripe/stripe-go"
    )

    Install via dotnet:

    dotnet add package Stripe.net
    dotnet restore

    Or using NuGet:

    PM> Install-Package Stripe.net

    Client-side

    The Android SDK is open source and fully documented.

    To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

    apply plugin: 'com.android.application'
    
    android { ... }
    
    dependencies {
      // ...
    
      // Stripe Android SDK
      implementation 'com.stripe:stripe-android:12.4.0'
    }
    

    Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

    import com.stripe.android.PaymentConfiguration
    
    class MyApp: Application() {
        override fun onCreate() {
            super.onCreate()
            PaymentConfiguration.init(applicationContext, "pk_test_TYooMQauvdEDq54NiTphI7jx")
        }
    }
    
    import com.stripe.android.PaymentConfiguration;
    
    public class MyApp extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            PaymentConfiguration.init(getApplicationContext(), "pk_test_TYooMQauvdEDq54NiTphI7jx");
        }
    }
    

    Our code samples also use OkHttp and GSON to make HTTP requests to a server.

    2 Create your checkout screen Client-side

    Securely collect card information on the client with CardInputWidget, a drop-in UI component provided by the SDK.

    CardInputWidget performs on-the-fly validation and formatting.

    Create an instance of the card component and a “Pay” button by adding the following to your checkout page’s layout:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            tools:showIn="@layout/activity_checkout"
            tools:context=".CheckoutActivity">
    
        <!--  ...  -->
    
        <com.stripe.android.view.CardInputWidget
                android:id="@+id/cardInputWidget"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="20dp"/>
    
        <Button
                android:text="Pay"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/payButton"
                android:layout_marginTop="20dp"
                app:layout_constraintTop_toBottomOf="@+id/cardInputWidget"
                app:layout_constraintStart_toStartOf="@+id/cardInputWidget"
                app:layout_constraintEnd_toEndOf="@+id/cardInputWidget"/>
    
          <!--  ...  -->
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Run your app, and make sure your checkout screen shows the card component and pay button.

    3 Collect card details Client-side

    When your customer is ready to check out, create a PaymentMethod with the details collected by the card element.

    class CheckoutActivity : AppCompatActivity() {
        private lateinit var stripe: Stripe
        // ...
    
        private fun pay() {
            val weakActivity = WeakReference<Activity>(this)
            // Collect card details on the client
            val cardInputWidget =
                findViewById<CardInputWidget>(R.id.cardInputWidget)
            val params = cardInputWidget.paymentMethodCreateParams
            if (params == null) {
                return
            }
    
            // Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API
            stripe = Stripe(applicationContext, PaymentConfiguration.getInstance(applicationContext).publishableKey)
            stripe.createPaymentMethod(params, object : ApiResultCallback<PaymentMethod> {
                // Create PaymentMethod failed
                override fun onError(e: Exception) {
                    displayAlert(weakActivity.get(), "Payment failed", "Error: $e")
                }
                override fun onSuccess(result: PaymentMethod) {
                    // Create a PaymentIntent on the server with a PaymentMethod
                    print("Created PaymentMethod")
                    pay(result.id, null)
                }
            })
        }
    
        private fun pay(paymentMethod: String?, paymentIntent: String?) {
            // ...
        }
    }
    
    public class CheckoutActivity extends AppCompatActivity {
        private Stripe stripe;
        // ...
    
        private void pay() {
            CardInputWidget cardInputWidget = findViewById(R.id.cardInputWidget);
            PaymentMethodCreateParams params = cardInputWidget.getPaymentMethodCreateParams();
    
            if (params == null) {
                return;
            }
    
            // Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API
            stripe = Stripe(applicationContext, PaymentConfiguration.getInstance(applicationContext).publishableKey);
            stripe.createPaymentMethod(params, new ApiResultCallback<PaymentMethod>() {
                @Override
                public void onSuccess(@NonNull PaymentMethod result) {
                    // Create and confirm the PaymentIntent by calling the sample server's /pay endpoint.
                    pay(result.id, null);
                }
    
                @Override
                public void onError(@NonNull Exception e) {
    
                }
            });
        }
    
        private void pay(@Nullable String paymentMethodId, @Nullable String paymentIntentId) {
            // ...continued in the next step
        }
    
    

    4 Create a PaymentIntent Client-side Server-side

    Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.

    On the server

    Add an endpoint that creates the PaymentIntent with the following parameters:

    • payment_method_id - the id of the PaymentMethod from the previous step
    • use_stripe_sdk
      • For apps that integrate with Stripe Android SDK v10.0.0 and above, the value of this parameter must be set to true
      • For apps that integrate with older SDK versions, do not pass this parameter
    • confirm - Set this to true to confirm the PaymentIntent

    If you want to save the card to reuse later, create a Customer to store the PaymentMethod and pass the following additional parameters when creating the PaymentIntent:

    • setup_future_usage. Set to off_session to tell Stripe that you plan to reuse this PaymentMethod for off-session payments when your customer is not present.
    • customer. Set to the ID of the Customer.
    • save_payment_method. Set to true to save the PaymentMethod to the Customer.

    Once the PaymentIntent is created, return the following to the client:

    • the PaymentIntent’s client secret
    • requiresAction: true if the PaymentIntent status is requires_action

    On the client

    Request a PaymentIntent from your server. This example passes the server a list of items to determine the price. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    class CheckoutActivity : AppCompatActivity() {
        private val backendUrl = "http://10.0.2.2:4242/"
        private val httpClient = OkHttpClient()
    
        private fun displayAlert(activity: Activity?, title: String, message: String, restartDemo: Boolean = false) {
            // omitted for brevity
        }
    
        private fun pay(paymentMethod: String?, paymentIntent: String?) {
            val weakActivity = WeakReference<Activity>(this)
            var json = ""
            if (!paymentMethod.isNullOrEmpty()) {
                json = """
                    {
                        "useStripeSdk":true,
                        "paymentMethodId":"$paymentMethod",
                        "currency":"usd",
                        "items": [
                            {"id":"photo_subscription"}
                        ]
                    }
                    """
            }
            else if (!paymentIntent.isNullOrEmpty()) {
                json = """
                    {
                        "paymentIntentId":"$paymentIntent"
                    }
                    """
            }
            // Create a PaymentIntent on the server
            val mediaType = "application/json; charset=utf-8".toMediaType()
            val body = json.toRequestBody(mediaType)
            val request = Request.Builder()
                .url(backendUrl + "pay")
                .post(body)
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        displayAlert(weakActivity.get(), "Payment failed", "Error: $e")
                    }
    
                    override fun onResponse(call: Call, response: Response) {
                        // Request failed
                        if (!response.isSuccessful) {
                            displayAlert(weakActivity.get(), "Payment failed", "Error: $response")
                        } else {
                            val responseData = response.body?.string()
                            var responseJson = JSONObject(responseData)
                            val payError: String? = responseJson.optString("error")
                            val clientSecret: String? = responseJson.optString("clientSecret")
                            val requiresAction: Boolean? = responseJson.optBoolean("requiresAction")
                            // Payment failed
                            if (payError != null && payError.isNotEmpty()) {
                                displayAlert(weakActivity.get(), "Payment failed", "Error: $payError")
                            }
                            // Payment succeeded
                            else if ((clientSecret != null && clientSecret.isNotEmpty())
                                && (requiresAction == null || requiresAction == false)) {
                                displayAlert(weakActivity.get(), "Payment succeeded", "$clientSecret", restartDemo = true)
                            }
                            // Payment requires additional actions
                            else if ((clientSecret != null && clientSecret.isNotEmpty())
                                && requiresAction == true) {
                                runOnUiThread {
                                    if (weakActivity.get() != null) {
                                        // ...continued in the next step
                                    }
                                }
                            }
                        }
                    }
                })
        }
    }
    
    public class CheckoutActivity extends AppCompatActivity {
        private static final String BACKEND_URL = "http://10.0.2.2:4242/";
        private OkHttpClient httpClient = new OkHttpClient();
    
        private void pay(@Nullable String paymentMethodId, @Nullable String paymentIntentId) {
            final MediaType mediaType = MediaType.get("application/json; charset=utf-8");
            final String json;
            if (paymentMethodId != null) {
                json = "{"
                        + "\"useStripeSdk\":true,"
                        + "\"paymentMethodId\":" + "\"" + paymentMethodId + "\","
                        + "\"currency\":\"usd\","
                        + "\"items\":["
                        + "{\"id\":\"photo_subscription\"}"
                        + "]"
                        + "}";
            } else {
                json = "{"
                        + "\"paymentIntentId\":" +  "\"" + paymentIntentId + "\""
                        + "}";
            }
            RequestBody body = RequestBody.create(json, mediaType);
            Request request = new Request.Builder()
                    .url(BACKEND_URL + "pay")
                    .post(body)
                    .build();
            httpClient
                    .newCall(request)
                    .enqueue(new PayCallback(this, stripe));
        }
    
        private void displayAlert(@NonNull String title, @NonNull String message, boolean restartDemo) {
            // ...omitted for brevity
        }
    
        private static final class PayCallback implements Callback {
            @NonNull private final WeakReference<CheckoutActivity> activityRef;
            @NonNull private final Stripe stripe;
    
            private PayCallback(@NonNull CheckoutActivity activity, @NonNull Stripe stripe) {
                this.activityRef = new WeakReference<>(activity);
                this.stripe = stripe;
            }
    
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                final CheckoutActivity activity = activityRef.get();
                if (activity == null) {
                    return;
                }
    
                activity.runOnUiThread(() -> {
                    Toast.makeText(activity, "Error: " + e.toString(), Toast.LENGTH_LONG).show();
                });
            }
    
            @Override
            public void onResponse(@NonNull Call call, @NonNull final Response response)
                    throws IOException {
                final CheckoutActivity activity = activityRef.get();
                if (activity == null) {
                    return;
                }
    
                if (!response.isSuccessful()) {
                    activity.runOnUiThread(() -> {
                        Toast.makeText(activity,
                                "Error: " + response.toString(), Toast.LENGTH_LONG).show();
                    });
                } else {
                    Gson gson = new Gson();
                    Type type = new TypeToken<Map<String, String>>(){}.getType();
                    Map<String, String> responseMap = gson.fromJson(response.body().string(), type);
    
                    String error = responseMap.get("error");
                    String paymentIntentClientSecret = responseMap.get("clientSecret");
                    String requiresAction = responseMap.get("requiresAction");
    
                    if (error != null) {
                        activity.displayAlert("Error", error, false);
                    } else if (paymentIntentClientSecret != null) {
                        if ("true".equals(requiresAction)) {
                            // ...continued in the next step
                        } else {
                            activity.displayAlert("Payment succeeded",
                                    paymentIntentClientSecret, true);
                        }
                    }
    
                }
            }
        }
    
    }
    

    5 Handle any next actions Client-side

    If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. On the client, pass the PaymentIntent ID to authenticatePayment. This SDK presents additional activities and walks the customer through authentication. See Supporting 3D Secure Authentication on Android to learn more.

    After handling a required action on the client, the status of the PaymentIntent is requires_confirmation. You must send the PaymentIntent ID to your backend and confirm it to finalize the payment. The payment attempt fails and transitions back to requires_payment_method if it is not confirmed again within one hour. This step enables your integration to synchronously fulfill the order on your backend and return the fulfillment result to your client.

    class CheckoutActivity : AppCompatActivity() {
        // ...
    
        private fun pay(paymentMethod: String?, paymentIntent: String?) {
            // ...
            val activity = weakActivity.get()!!
            stripe.authenticatePayment(activity, clientSecret)
            // ...
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            val weakActivity = WeakReference<Activity>(this)
    
            // Handle the result of stripe.authenticatePayment
            stripe.onPaymentResult(requestCode, data, object : ApiResultCallback<PaymentIntentResult> {
                override fun onSuccess(result: PaymentIntentResult) {
                    val paymentIntent = result.intent
                    val status = paymentIntent.status
                    if (status == StripeIntent.Status.Succeeded) {
                        val gson = GsonBuilder().setPrettyPrinting().create()
                        displayAlert(weakActivity.get(), "Payment succeeded", gson.toJson(paymentIntent), restartDemo = true)
                    } else if (status == StripeIntent.Status.RequiresPaymentMethod) {
                        // Payment failed – allow retrying using a different payment method
                        displayAlert(weakActivity.get(), "Payment failed", paymentIntent.lastPaymentError!!.message ?: "")
                    }
                    else if (status == StripeIntent.Status.RequiresConfirmation) {
                        print("Re-confirming PaymentIntent after handling a required action")
                        pay(null, paymentIntent.id)
                    }
                    else {
                        displayAlert(weakActivity.get(), "Payment status unknown", "unhandled status: $status", restartDemo = true)
                    }
                }
    
                override fun onError(e: Exception) {
                    // Payment request failed – allow retrying using the same payment method
                    displayAlert(weakActivity.get(), "Payment failed", e.toString())
                }
            })
        }
    }
    
    public class CheckoutActivity extends AppCompatActivity {
    
        // ...
    
        private static final class PayCallback implements Callback {
            // ...
            activity.runOnUiThread(() ->
                    stripe.authenticatePayment(activity, paymentIntentClientSecret));
            // ...
        }
    
        private static final class PaymentResultCallback
                implements ApiResultCallback<PaymentIntentResult> {
            private final WeakReference<CheckoutActivity> activityRef;
    
            PaymentResultCallback(@NonNull CheckoutActivity activity) {
                activityRef = new WeakReference<>(activity);
            }
    
            @Override
            public void onSuccess(@NonNull PaymentIntentResult result) {
                final CheckoutActivity activity = activityRef.get();
                if (activity == null) {
                    return;
                }
    
                PaymentIntent paymentIntent = result.getIntent();
                PaymentIntent.Status status = paymentIntent.getStatus();
                if (status == PaymentIntent.Status.Succeeded) {
                    // Payment completed successfully
                    activity.runOnUiThread(() -> {
                        Gson gson = new GsonBuilder().setPrettyPrinting().create();
                        activity.displayAlert("Payment completed",
                                gson.toJson(paymentIntent), true);
                    });
                } else if (status == PaymentIntent.Status.RequiresPaymentMethod) {
                    // Payment failed – allow retrying using a different payment method
                    activity.runOnUiThread(() -> activity.displayAlert("Payment failed",
                            paymentIntent.getLastPaymentError().message, false));
                } else if (status == PaymentIntent.Status.RequiresConfirmation) {
                    // After handling a required action on the client, the status of the PaymentIntent is
                    // requires_confirmation. You must send the PaymentIntent ID to your backend
                    // and confirm it to finalize the payment. This step enables your integration to
                    // synchronously fulfill the order on your backend and return the fulfillment result
                    // to your client.
                    activity.pay(null, paymentIntent.getId());
                }
            }
    
            @Override
            public void onError(@NonNull Exception e) {
                final CheckoutActivity activity = activityRef.get();
                if (activity == null) {
                    return;
                }
    
                // Payment request failed – allow retrying using the same payment method
                activity.runOnUiThread(() -> {
                    activity.displayAlert("Error", e.toString(), false);
                });
            }
        }
    }
    

    The result of payment authentication returns to your calling Activity via Activity#onActivityResult(). Handle the result by calling Stripe#onPaymentResult() within Activity#onActivityResult(). The PaymentIntentResult returned in ApiResultCallback#onSuccess() has two getters:

    • getIntent() - a PaymentIntent object that has been retrieved after confirmation/authentication
    • getOutcome() - a StripeIntentResult.Status value that indicates the outcome of payment authentication
      • SUCCEEDED - payment authentication succeeded
      • FAILED - payment authentication failed
      • CANCELED - the customer canceled required payment authentication
      • TIMEDOUT - the payment authentication attempt timed-out

    6 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000002500003155 Requires authentication. Stripe will trigger a modal asking for the customer to authenticate.
    4000000000009995 Always fails with a decline code of insufficient_funds.

    For the full list of test cards see our guide on testing.

    Next steps

    Now that you have a working integration, test your payment flows using the test card numbers and PaymentMethods:

    Was this page helpful?

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

    On this page