Using Payment Intents on iOS

    Learn how to build an iOS application that uses the Payment Intents API to accept card payments.

    There are two ways to accept card payments with the Payment Intents API depending on how you want to complete the payment: automatic confirmation and manual confirmation. See the Payment Intents API Overview for a detailed comparison.

    • The automatic confirmation flow confirms the payment on the client and uses webhooks to inform you when the payment is successful.
    • The manual confirmation flow confirms the payment on the server and lets you fulfill on your server immediately after the payment succeeds.

    Automatic confirmation

    Automatic confirmation is the fastest way to get started with the Payment Intents API. Because some payment methods have asynchronous flows, such as 3D Secure authentication for cards, the automatic confirmation integration requires using webhooks to handle any post-payment logic.

    A diagram showing the auto confirmation server to client to webhooks flow

    1. Create a PaymentIntent on the server
    2. Pass the PaymentIntent’s client secret to the client
    3. Collect card information and create a STPPaymentMethodCardParams object
    4. Confirm the PaymentIntent and authenticate if necessary
    5. Asynchronously fulfill the customer’s order

    Step 1: Create a PaymentIntent on the server

    A PaymentIntent is an object that represents your intent to collect payment from a customer, tracking the lifecycle of the payment process through each stage. When you create a PaymentIntent, specify the amount of money that you wish to collect and the currency. The following example shows how to create a PaymentIntent on your server:

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = Stripe::PaymentIntent.create({
        amount: 1099,
        currency: 'usd',
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = stripe.PaymentIntent.create(
      amount=1099,
      currency='usd',
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    $intent = \Stripe\PaymentIntent::create([
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> paymentintentParams = new HashMap<String, Object>();
    paymentintentParams.put("amount", 1099);
    paymentintentParams.put("currency", "usd");
    
    PaymentIntent.create(paymentintentParams);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1099,
        currency: 'usd',
      });
    })();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    paymentintent.New(params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
    };
    service.Create(options);
    

    We recommend creating a PaymentIntent as soon as the amount is known, such as when the customer begins the checkout process. If the amount changes, for example when the cart subtotal updates or when shipping fees and taxes are calculated, you can update the amount. Separate authorization and capture is also supported for card payments.

    curl https://api.stripe.com/v1/payment_intents/pi_1DRuHnHgsMRlo4MtwuIAUe6u \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1499
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    Stripe::PaymentIntent.update(
        'pi_1DRuHnHgsMRlo4MtwuIAUe6u',
        {
            amount: 1499,
        }
    )
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.PaymentIntent.modify(
      'pi_1DRuHnHgsMRlo4MtwuIAUe6u',
      amount=1499
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    \Stripe\PaymentIntent::update(
        'pi_1DRuHnHgsMRlo4MtwuIAUe6u',
        [
            'amount' => 1499,
        ]
    );
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    PaymentIntent intent = PaymentIntent.retrieve("pi_1DRuHnHgsMRlo4MtwuIAUe6u");
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("amount", 1499);
    intent.update(params);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      await stripe.paymentIntents.update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", {
        amount: 1499
      })
    })();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1499),
    }
    intent, err := paymentintent.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentUpdateOptions
    {
        Amount = 1499,
    };
    service.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", options);
    

    Step 2: Pass the PaymentIntent’s client secret to the client

    The PaymentIntent object contains a client secret, a unique key that you pass to your app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint that you set up in step 1 to create a PaymentIntent.

    MyAPIClient.createPaymentIntent(amount: 100, currency: "usd") { result in
      switch (result) {
        case .success(let clientSecret):
          // Hold onto clientSecret for Step 4
        case .failure(let error):
          // Handle the error
      }
    }
    [MyAPIClient createPaymentIntentWithAmount:100
                      currency:@"usd"
                      completion:^(NSString *clientSecret, NSError *error) {
                        if (error != nil) {
                          // Handle the error
                        } else {
                          // Hold onto clientSecret for Step 4
                        }
                      }];

    Each PaymentIntent typically correlates with a single “cart” or customer session in your application. You can create the PaymentIntent during checkout and store its ID on the user’s cart in the data model of your application, retrieving it again as necessary.

    You can use the client secret to complete the payment process with the amount specified on the PaymentIntent. Do not log it, embed it in URLs, or expose it to anyone other than the customer.

    Step 3: Collect card information

    If you already have a PaymentMethod or Source that you would like to associate with the PaymentIntent, such as a STPPaymentMethod from an STPPaymentContext integration, skip this step.

    In your application, collect payment information through your own form and pass the collected information into new STPPaymentMethodCardParams and STPPaymentMethodBillingDetails instances.

    let cardParams = STPPaymentMethodCardParams()
    let billingDetails = STPPaymentMethodBillingDetails()
    // Fill in card, billing details
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new];
    // Fill in card, billing details
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:billingDetails metadata:nil];

    Apple Pay is supported. Instead of collecting card details:

    Step 4: Confirm the PaymentIntent

    To initiate payment collection, you must confirm that your customer intends to pay with the provided payment details.

    Add the desired payment method to an STPPaymentIntentParams object initialized with the PaymentIntent’s client secret. There are 3 ways to do this:

    The following code demonstrates how to create the STPPaymentIntentParams object from an STPPaymentMethodParams instance.

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
    paymentIntentParams.paymentMethodParams = paymentMethodParams
    STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:client_secret];
    paymentIntentParams.paymentMethodParams = paymentMethodParams;

    If you’d like to send additional data, you can assign values to the relevant properties on the STPPaymentIntentParams object. Any extra parameters that you add are included in the request to Stripe. For example, to send an email to the customer on payment confirmation, use the receiptEmail property.

    To finish confirming the payment and automatically handle any additional required actions for authentication, pass the STPPaymentIntentParams object to the confirmPayment method on STPPaymentHandler sharedManager.

    If the customer must perform 3D Secure authentication to complete the payment, STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks them through that process. See Supporting 3D Secure Authentication on iOS to learn more.

    The following sample code assumes that MyCheckoutViewController should present any additional view controllers and implements STPAuthenticationContext.

    let paymentManager = STPPaymentHandler.shared()
    paymentManager.confirmPayment(paymentIntentParams, with: self, returnURL: nil, completion { (status, paymentIntent, error) in
      switch (status) {
        case .failed:
          // Handle error
        case .canceled:
          // Handle cancel
        case .succeeded:
          // Payment Intent is confirmed
      }
    })
    [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams
                            withAuthenticationContext:self
                                            returnURL:nil
                                           completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * error) {
        switch (status) {
            case STPPaymentHandlerActionStatusFailed:
                // Handle the error by inspecting paymentIntent.status
                break;
            case STPPaymentHandlerActionStatusCanceled:
                // Handle cancel
                break;
            case STPPaymentHandlerActionStatusSucceeded:
                // Payment Intent is confirmed
                break;
        }
    }];

    Step 5: Asynchronously fulfill the customer’s order

    You can use the status returned by STPPaymentHandler to provide immediate feedback to your customers when the payment completes on the client. However, your integration should not attempt to handle order fulfillment on the client side because it is possible for customers to leave the app after payment is complete but before the fulfillment process initiates. Instead, you will need to handle asynchronous events in order to be notified and drive fulfillment when the payment succeeds.

    There are several ways you can fulfill an order:

    When you attempt to collect payment from a customer, the PaymentIntent creates a Charge object. You can inspect the PaymentIntent’s charges.data property to obtain the latest charge. For the complete list of attempted charges, use the Charges API to list all charges with the PaymentIntent’s ID:

    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    charges = Stripe::Charge.list({
      payment_intent: '{{PAYMENT_INTENT_ID}}',
      # Limit the number of objects to return (the default is 10)
      limit: 3,
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    charges = stripe.Charge.list(
      payment_intent = '{{PAYMENT_INTENT_ID}}',
      # Limit the number of objects to return (the default is 10)
      limit = 3,
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    $charges = \Stripe\Charge::all([
        'payment_intent' => '{{PAYMENT_INTENT_ID}}',
        // Limit the number of objects to return (the default is 10)
        'limit' => 3,
    ]);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("payment_intent", "{{PAYMENT_INTENT_ID}}");
    // Limit the number of objects to return (the default is 10)
    chargeParams.put("limit", "3");
    
    try {
      ChargeCollection charges = Charge.list(chargeParams);
    } catch (StripeException e) {
      // handle error from Stripe API
    }
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      const charges = stripe.charges.list({
        payment_intent: '{{PAYMENT_INTENT_ID}}',
        // Limit the number of objects to return (the default is 10)
        limit: 3,
      });
    })();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.ChargeListParams{}
    params.Filters.AddFilter("payment_intent", "", "{{PAYMENT_INTENT_ID}}")
    // Limit the number of objects to return (the default is 10)
    params.Filters.AddFilter("limit", "", "3")
    i := charge.List(params)
    for i.Next() {
      c := i.Charge()
    }
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new ChargeService();
    var options = new ChargeListOptions
    {
        PaymentIntentId = "{{PAYMENT_INTENT_ID}}",
        // Limit the number of objects to return (the default is 10)
        Limit = 3,
    };
    var charges = service.List(options);
    

    The charges are listed in reverse chronological order, so the most recent charge is first in the list. Note that the list includes any unsuccessful charges created during the payment process in addition to the final successful charge.

    Next, test your integration to make sure you’re correctly handling cards that require additional authentication.

    Manual confirmation

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

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

    1. Collect card information and create a PaymentMethod
    2. Create and confirm a PaymentIntent on the server
    3. Authenticate the payment if necessary
    4. Confirm the PaymentIntent again on the server

    Step 1: Collect card information

    If you already have a PaymentMethod or Source that you would like to associate with the PaymentIntent, such as a STPPaymentMethod from an STPPaymentContext integration, skip this step.

    In your application, collect card details from the customer and pass the collected information into new STPPaymentMethodCardParams and STPPaymentMethodBillingDetails instances to create a PaymentMethod.

    let cardParams = STPPaymentMethodCardParams()
    let billingDetails = STPPaymentMethodBillingDetails()
    // Fill in card, billing details
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
    STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams, completion: completionClosure)
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new];
    // Fill in card, billing details
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:billingDetails metadata:nil];
    [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:completionBlock];

    Apple Pay is supported. Instead of collecting card details:

    Step 2: Create and confirm a PaymentIntent on the server

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

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

    The endpoint should accept the following parameters:

    Don’t return the entire Payment Intent in your backend response; always re-fetch the Payment Intent on the client instead.

    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 \
      -d return_url="{{RETURN_URL}}" \
      -d use_stripe_sdk=true
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    post '/ajax/confirm_payment' do
      data = JSON.parse(request.body.read.to_s)
    
      begin
        if data['payment_method_id']
          # Create the PaymentIntent
          intent = Stripe::PaymentIntent.create(
            payment_method: data['payment_method_id'],
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true,
            return_url: data['return_url'],
            use_stripe_sdk: 'true',
          )
        elsif data['payment_intent_id']
          intent = Stripe::PaymentIntent.confirm(data['payment_intent_id'])
        end
      rescue Stripe::CardError => e
        # Display error on client
        return [200, { error: e.message }.to_json]
      end
    
      return generate_payment_response(intent)
    end
    
    def generate_payment_response(intent)
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action'
        # Tell the client to handle the action
        [
          200,
          {
            requires_action: true,
            payment_intent_client_secret: intent.client_secret
          }.to_json
        ]
      elsif intent.status == 'succeeded'
        # The payment didn’t need any additional actions and is completed!
        # Handle post-payment fulfillment
        [200, { success: true }.to_json]
      else
        # Invalid status
        return [500, { error: 'Invalid PaymentIntent status' }.to_json]
      end
    end
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    @app.route('/ajax/confirm_payment', methods=['POST'])
    def confirm_payment():
      data = request.get_json()
      intent = None
    
      try:
        if 'payment_method_id' in data:
          # Create the PaymentIntent
          intent = stripe.PaymentIntent.create(
            payment_method = data['payment_method_id'],
            amount = 1099,
            currency = 'usd',
            confirmation_method = 'manual',
            confirm = True,
            return_url = data['return_url'],
            use_stripe_sdk = True,
          )
        elif 'payment_intent_id' in data:
          intent = stripe.PaymentIntent.confirm(data['payment_intent_id'])
      except stripe.error.CardError as e:
        # Display error on client
        return json.dumps({'error': e.user_message}), 200
    
      return generate_payment_response(intent)
    
    def generate_payment_response(intent):
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action':
        # 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,
            'return_url' => $json_obj->return_url,
            'use_stripe_sdk' => true,
          ]);
        }
        if (isset($json_obj->payment_intent_id)) {
          $intent = \Stripe\PaymentIntent::retrieve(
            $json_obj->payment_intent_id
          );
          $intent->confirm();
        }
        generatePaymentResponse($intent);
      } catch (\Stripe\Error\Base $e) {
        # Display error on client
        echo json_encode([
          'error' => $e->getMessage()
        ]);
      }
    
      function generatePaymentResponse($intent) {
        # Note that if your API version is before 2019-02-11, 'requires_action'
        # appears as 'requires_source_action'.
        if ($intent->status == 'requires_action') {
          # 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;
        @SerializedName("return_url")
        String returnURL;
    
        public String getPaymentMethodId() {
          return paymentMethodId;
        }
    
        public String getPaymentIntentId() {
          return paymentIntentId;
        }
    
        public String getReturnURL() {
          return returnURL;
        }
      }
    
      /**
       * Your application.
       */
      public static void main(String[] args) {
    
        Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");
    
        post(new Route("/ajax/confirm_payment") {
    
          @Override
          public Object handle(Request request, Response response) {
            Gson gson = new Gson();
            ConfirmPaymentRequest confirmRequest =
                gson.fromJson(request.body(), ConfirmPaymentRequest.class);
            PaymentIntent intent;
            try {
              if (confirmRequest.getPaymentMethodId() != null) {
                PaymentIntentCreateParams createParams = PaymentIntentCreateParams.builder()
                    .setAmount(1099)
                    .setCurrency("usd")
                    .setConfirm(true)
                    .setPaymentMethod(confirmRequest.paymentMethodId)
                    .setConfirmationMethod(PaymentIntentCreateParams.ConfirmationMethod.MANUAL)
                    .setReturnURL(confirmRequest.returnURL)
                    .setUseStripeSDK(true)
                    .build();
                intent = PaymentIntent.create(createParams);
              } else if (confirmRequest.getPaymentIntentId() != null) {
                intent = PaymentIntent.retrieve(confirmRequest.getPaymentIntentId());
                intent = intent.confirm();
              }
    
              Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              Map<String, Object> errorResponse = new HashMap<>();
              errorResponse.put("message", e.getMessage());
              return gson.toJson(errorResponse);
            }
          }
    
          private Map<String, Object> generatePaymentResponse(Response response,
                                                              PaymentIntent intent) {
            response.type("application/json");
            Map<String, Object> responseData = new HashMap<>();
            // Note that if your API version is before 2019-02-11, 'requires_action'
            // appears as 'requires_source_action'.
            if (intent.getStatus().equals("requires_action")) {
              responseData.put("requires_action", true);
              responseData.put("payment_intent_client_secret", intent.getClientSecret());
            } else if (intent.getStatus().equals("succeeded")) {
              responseData.put("success", true);
            } else {
              // invalid status
              responseData.put("Error", "Invalid status");
              response.status(500);
              return responseData;
            }
            response.status(200);
            return responseData;
          }
        });
      }
    }
    
    // Using Express
    const express = require('express');
    const app = express();
    app.use(express.json());
    
    app.post('/ajax/confirm_payment', async (request, response) => {
      try {
        let intent;
        if (request.body.payment_method_id) {
          // Create the PaymentIntent
          intent = await stripe.paymentIntents.create({
            payment_method: request.body.payment_method_id,
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true
            returnURL: request.body.return_url,
            use_stripe_sdk: true
          });
        } else if (request.body.payment_intent_id) {
          intent = await stripe.paymentIntents.confirm(
            request.body.payment_intent_id
          );
        }
        // Send the response to the client
        response.send(generate_payment_response(intent));
      } catch (e) {
        // Display error on client
        return response.send({ error: e.message });
      }
    });
    
    const generate_payment_response = (intent) => {
      // Note that if your API version is before 2019-02-11, 'requires_action'
      // appears as 'requires_source_action'.
      if (
        intent.status === 'requires_action'
      ) {
        // 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"`
      ReturnURL *string `json:"return_url"`
    }
    
    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 {
    
        // Tell the client to handle the action
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(ConfirmPaymentResponse{
          RequiresAction: true,
          PaymentIntentClientSecret: &intent.ClientSecret,
        })
    
      } else if intent.Status == stripe.PaymentIntentStatusSucceeded {
    
        // The payment didn’t need any additional actions and completed!
        // Handle post-payment fulfillment
        w.WriteHeader(http.StatusOK)
        fmt.Fprint(w, "Success")
    
      } else {
    
        // Invalid status
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Invalid Payment Intent status: %s", intent.Status)
    
      }
    }
    
    func main() {
      // Initialize stripe-go with Stripe secret key from environment
      stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
    
      // AJAX endpoint when `/ajax/confirm_payment` is called from client
      http.HandleFunc("/ajax/confirm_payment",
        func(w http.ResponseWriter, req *http.Request) {
          var confirmPaymentRequest ConfirmPaymentRequest
          json.NewDecoder(req.Body).Decode(&confirmPaymentRequest)
    
          var intent *stripe.PaymentIntent
          var err error
    
          if confirmPaymentRequest.PaymentMethodId != nil {
            // Create the PaymentIntent
            params := &stripe.PaymentIntentParams{
              PaymentMethod: stripe.String(*confirmPaymentRequest.PaymentMethodId),
              Amount: stripe.Int64(1099),
              Currency: stripe.String(string(stripe.CurrencyUSD)),
              ConfirmationMethod: stripe.String(string(
                stripe.PaymentIntentConfirmationMethodManual,
              )),
              Confirm: stripe.Bool(true),
              ReturnURL: stripe.String(*confirmPaymentRequest.ReturnURL),
              UseStripeSDK: 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; }
    
            [JsonProperty("return_url")]
            public string ReturnURL { get; set; }
        }
    
        // AJAX endpoint when `/ajax/confirm_payment` is called from client
        [Route("/ajax/confirm_payment")]
        public class ConfirmPaymentController : Controller
        {
            public IActionResult Index([FromBody] ConfirmPaymentRequest request)
            {
                var paymentIntentService = new PaymentIntentService();
                PaymentIntent paymentIntent = null;
    
                try
                {
                    if (request.PaymentMethodId != null)
                    {
                        // Create the PaymentIntent
                        var createOptions = new PaymentIntentCreateOptions
                        {
                            PaymentMethodId = request.PaymentMethodId,
                            Amount = 1099,
                            Currency = "usd",
                            ConfirmationMethod = "manual",
                            Confirm = true,
                            ReturnURL = request.ReturnURL,
                            UseStripeSDK = 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")
                {
                    // 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 are migrating an existing STPPaymentContext integration, your endpoint creates the PaymentIntent by passing the STPaymentResult paymentMethod stripeId received from the client as the paymentMethod parameter.

    Step 3: Authenticate the payment if necessary

    If the PaymentIntent requires additional action from the customer, such as authenticating with 3D Secure, pass the Payment Intent client secret from the previous step to STPPaymentHandler handleNextActionForPayment. STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks them through that process. See Supporting 3D Secure Authentication on iOS to learn more.

    The following sample code assumes that MyCheckoutViewController should present any additional view controllers and implements STPAuthenticationContext.

    // Call the endpoint you set up in step 2 with the payment method from step 1
    MyAPIClient.createAndConfirmPaymentIntent(paymentMethodId: paymentMethodId) { result in
      guard case .success(let response) = result else {
        // Handle error
        return
      }
    
      if (response.requiresAction) {
        STPPaymentHandler.shared().handleNextAction(forPayment: response.clientSecret, with: self, returnURL: nil) { (status, paymentIntent, error) in
          switch (status) {
            case .succeeded:
              // ...Continued in Step 4
            case .canceled:
              // Handle cancel
            case .failed:
              // Handle error
          }
        }
      } else {
        // Show success message
      }
    }
    // Call the endpoint you set up in step 2 with the payment method from step 1
    [MyAPIClient createAndConfirmPaymentIntentWithPaymentMethodId:paymentMethodId completion:^(NSString *paymentIntentClientSecret, BOOL requiresAction, NSError *error) {
      if (error != nil) {
        // Handle the error
        return;
      }
    
      if (requiresAction) {
        [[STPPaymentHandler sharedHandler] handleNextActionForPayment:paymentIntentClientSecret withAuthenticationContext:self returnURL:nil completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * handlerError) {
          switch (status) {
            case STPPaymentHandlerActionStatusSucceeded:
              // ...Continued in Step 4
            case STPPaymentHandlerActionStatusCanceled:
              // Handle cancel
            case STPPaymentHandlerActionStatusFailed:
              // Handle error
          }
        }];
      } else {
          // Show success message
      }
    }];

    Step 4: Confirm the PaymentIntent again on the server

    If handling next actions in step 3 was necessary, use the same endpoint you set up in Step 2 to confirm the PaymentIntent again to finalize the payment and fulfill the order.

    Server-side

    curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -X POST
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    post '/ajax/confirm_payment' do
      data = JSON.parse(request.body.read.to_s)
    
      begin
        if data['payment_method_id']
          # Create the PaymentIntent
          intent = Stripe::PaymentIntent.create(
            payment_method: data['payment_method_id'],
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true,
            return_url: data['return_url'],
            use_stripe_sdk: 'true',
          )
        elsif data['payment_intent_id']
          intent = Stripe::PaymentIntent.confirm(data['payment_intent_id'])
        end
      rescue Stripe::CardError => e
        # Display error on client
        return [200, { error: e.message }.to_json]
      end
    
      return generate_payment_response(intent)
    end
    
    def generate_payment_response(intent)
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action'
        # Tell the client to handle the action
        [
          200,
          {
            requires_action: true,
            payment_intent_client_secret: intent.client_secret
          }.to_json
        ]
      elsif intent.status == 'succeeded'
        # The payment didn’t need any additional actions and is completed!
        # Handle post-payment fulfillment
        [200, { success: true }.to_json]
      else
        # Invalid status
        return [500, { error: 'Invalid PaymentIntent status' }.to_json]
      end
    end
    
    # AJAX endpoint when `/ajax/confirm_payment` is called from client
    @app.route('/ajax/confirm_payment', methods=['POST'])
    def confirm_payment():
      data = request.get_json()
      intent = None
    
      try:
        if 'payment_method_id' in data:
          # Create the PaymentIntent
          intent = stripe.PaymentIntent.create(
            payment_method = data['payment_method_id'],
            amount = 1099,
            currency = 'usd',
            confirmation_method = 'manual',
            confirm = True,
            return_url = data['return_url'],
            use_stripe_sdk = True,
          )
        elif 'payment_intent_id' in data:
          intent = stripe.PaymentIntent.confirm(data['payment_intent_id'])
      except stripe.error.CardError as e:
        # Display error on client
        return json.dumps({'error': e.user_message}), 200
    
      return generate_payment_response(intent)
    
    def generate_payment_response(intent):
      # Note that if your API version is before 2019-02-11, 'requires_action'
      # appears as 'requires_source_action'.
      if intent.status == 'requires_action':
        # 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,
            'return_url' => $json_obj->return_url,
            'use_stripe_sdk' => true,
          ]);
        }
        if (isset($json_obj->payment_intent_id)) {
          $intent = \Stripe\PaymentIntent::retrieve(
            $json_obj->payment_intent_id
          );
          $intent->confirm();
        }
        generatePaymentResponse($intent);
      } catch (\Stripe\Error\Base $e) {
        # Display error on client
        echo json_encode([
          'error' => $e->getMessage()
        ]);
      }
    
      function generatePaymentResponse($intent) {
        # Note that if your API version is before 2019-02-11, 'requires_action'
        # appears as 'requires_source_action'.
        if ($intent->status == 'requires_action') {
          # 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;
        @SerializedName("return_url")
        String returnURL;
    
        public String getPaymentMethodId() {
          return paymentMethodId;
        }
    
        public String getPaymentIntentId() {
          return paymentIntentId;
        }
    
        public String getReturnURL() {
          return returnURL;
        }
      }
    
      /**
       * Your application.
       */
      public static void main(String[] args) {
    
        Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");
    
        post(new Route("/ajax/confirm_payment") {
    
          @Override
          public Object handle(Request request, Response response) {
            Gson gson = new Gson();
            ConfirmPaymentRequest confirmRequest =
                gson.fromJson(request.body(), ConfirmPaymentRequest.class);
            PaymentIntent intent;
            try {
              if (confirmRequest.getPaymentMethodId() != null) {
                PaymentIntentCreateParams createParams = PaymentIntentCreateParams.builder()
                    .setAmount(1099)
                    .setCurrency("usd")
                    .setConfirm(true)
                    .setPaymentMethod(confirmRequest.paymentMethodId)
                    .setConfirmationMethod(PaymentIntentCreateParams.ConfirmationMethod.MANUAL)
                    .setReturnURL(confirmRequest.returnURL)
                    .setUseStripeSDK(true)
                    .build();
                intent = PaymentIntent.create(createParams);
              } else if (confirmRequest.getPaymentIntentId() != null) {
                intent = PaymentIntent.retrieve(confirmRequest.getPaymentIntentId());
                intent = intent.confirm();
              }
    
              Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              Map<String, Object> errorResponse = new HashMap<>();
              errorResponse.put("message", e.getMessage());
              return gson.toJson(errorResponse);
            }
          }
    
          private Map<String, Object> generatePaymentResponse(Response response,
                                                              PaymentIntent intent) {
            response.type("application/json");
            Map<String, Object> responseData = new HashMap<>();
            // Note that if your API version is before 2019-02-11, 'requires_action'
            // appears as 'requires_source_action'.
            if (intent.getStatus().equals("requires_action")) {
              responseData.put("requires_action", true);
              responseData.put("payment_intent_client_secret", intent.getClientSecret());
            } else if (intent.getStatus().equals("succeeded")) {
              responseData.put("success", true);
            } else {
              // invalid status
              responseData.put("Error", "Invalid status");
              response.status(500);
              return responseData;
            }
            response.status(200);
            return responseData;
          }
        });
      }
    }
    
    // Using Express
    const express = require('express');
    const app = express();
    app.use(express.json());
    
    app.post('/ajax/confirm_payment', async (request, response) => {
      try {
        let intent;
        if (request.body.payment_method_id) {
          // Create the PaymentIntent
          intent = await stripe.paymentIntents.create({
            payment_method: request.body.payment_method_id,
            amount: 1099,
            currency: 'usd',
            confirmation_method: 'manual',
            confirm: true
            returnURL: request.body.return_url,
            use_stripe_sdk: true
          });
        } else if (request.body.payment_intent_id) {
          intent = await stripe.paymentIntents.confirm(
            request.body.payment_intent_id
          );
        }
        // Send the response to the client
        response.send(generate_payment_response(intent));
      } catch (e) {
        // Display error on client
        return response.send({ error: e.message });
      }
    });
    
    const generate_payment_response = (intent) => {
      // Note that if your API version is before 2019-02-11, 'requires_action'
      // appears as 'requires_source_action'.
      if (
        intent.status === 'requires_action'
      ) {
        // 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"`
      ReturnURL *string `json:"return_url"`
    }
    
    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 {
    
        // Tell the client to handle the action
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(ConfirmPaymentResponse{
          RequiresAction: true,
          PaymentIntentClientSecret: &intent.ClientSecret,
        })
    
      } else if intent.Status == stripe.PaymentIntentStatusSucceeded {
    
        // The payment didn’t need any additional actions and completed!
        // Handle post-payment fulfillment
        w.WriteHeader(http.StatusOK)
        fmt.Fprint(w, "Success")
    
      } else {
    
        // Invalid status
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Invalid Payment Intent status: %s", intent.Status)
    
      }
    }
    
    func main() {
      // Initialize stripe-go with Stripe secret key from environment
      stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
    
      // AJAX endpoint when `/ajax/confirm_payment` is called from client
      http.HandleFunc("/ajax/confirm_payment",
        func(w http.ResponseWriter, req *http.Request) {
          var confirmPaymentRequest ConfirmPaymentRequest
          json.NewDecoder(req.Body).Decode(&confirmPaymentRequest)
    
          var intent *stripe.PaymentIntent
          var err error
    
          if confirmPaymentRequest.PaymentMethodId != nil {
            // Create the PaymentIntent
            params := &stripe.PaymentIntentParams{
              PaymentMethod: stripe.String(*confirmPaymentRequest.PaymentMethodId),
              Amount: stripe.Int64(1099),
              Currency: stripe.String(string(stripe.CurrencyUSD)),
              ConfirmationMethod: stripe.String(string(
                stripe.PaymentIntentConfirmationMethodManual,
              )),
              Confirm: stripe.Bool(true),
              ReturnURL: stripe.String(*confirmPaymentRequest.ReturnURL),
              UseStripeSDK: 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; }
    
            [JsonProperty("return_url")]
            public string ReturnURL { get; set; }
        }
    
        // AJAX endpoint when `/ajax/confirm_payment` is called from client
        [Route("/ajax/confirm_payment")]
        public class ConfirmPaymentController : Controller
        {
            public IActionResult Index([FromBody] ConfirmPaymentRequest request)
            {
                var paymentIntentService = new PaymentIntentService();
                PaymentIntent paymentIntent = null;
    
                try
                {
                    if (request.PaymentMethodId != null)
                    {
                        // Create the PaymentIntent
                        var createOptions = new PaymentIntentCreateOptions
                        {
                            PaymentMethodId = request.PaymentMethodId,
                            Amount = 1099,
                            Currency = "usd",
                            ConfirmationMethod = "manual",
                            Confirm = true,
                            ReturnURL = request.ReturnURL,
                            UseStripeSDK = 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")
                {
                    // 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" });
                }
            }
        }
    }
    

    Client-side

    This example code continues from step 3.

    // case .succeeded:
    if (paymentIntent.status == .requiresConfirmation) {
        // Confirm the PaymentIntent on your backend again to complete the payment.
        MyAPIClient.confirmPaymentIntent(paymentIntent.clientSecret) { result in
            guard let response = result, response.success else {
                // Handle the error
                return
            }
            // Show success message
        }
    } else {
        // Show success message
    }
    // case STPPaymentHandlerActionStatusSucceeded:
    if (paymentIntent.status == STPPaymentIntentStatusRequiresConfirmation) {
        // Confirm the PaymentIntent on your backend again to complete the payment.
        [MyApiClient confirmPaymentIntent:paymentIntent.clientSecret completion:^(MyConfirmResponse response, NSError *error) {
            if (error || !response.success) {
                // Handle the error
                return;
            }
            // Show success message
        }];
        break;
    } else {
        // Show success message
    }

    Test the integration

    It’s important to thoroughly test your integration to make sure you’re correctly handling cards that require additional authentication and cards that don’t. Use these card numbers in test mode with any expiration date in the future and any three digit CVC code to validate your integration when authentication is required and when it’s not required.

    Number Authentication Description
    4000002500003155 Required on setup or first transaction This test card requires authentication for one-time payments. However, if you set up this card using the Setup Intents API and use the saved card for subsequent payments, no further authentication is needed.
    4000002760003184 Required This test card requires authentication on all transactions.
    4000008260003178 Required This test card requires authentication, but payments will be declined with an insufficient_funds failure code after successful authentication.
    4000000000003055 Supported This test card supports authentication via 3D Secure 2, but does not require it. Payments using this card do not require additional authentication in test mode unless your test mode Radar rules request authentication.

    Use these cards in your application or the payments demo to see the different behavior.

    Next steps

    You now have an iOS integration that can accept card payments with the Payment Intents API, prompting customers for authentication if needed.

    Was this page helpful? Yes No

    Send

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

    Questions?

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

    On this page