Migrating to Payment Intents with Manual Confirmation

    Learn how to migrate an existing cards and Charges API integration to the Payment Intents API using manual confirmation.

    Strategy for migration

    Migrating your payment flow can be daunting. It is safe to incrementally adopt the Payment Intents API and use it in parallel with the Charges API. To this end, you can split up the migration into the following steps:

    1. Update your API version and your client library.
    2. If applicable, migrate code that reads from Charge properties so that you have a consistent read path between charges created by the Charges API and charges created by the Payment Intents API. This ensures a read-side integration that works for both your old and new payments integrations.
    3. Migrate your existing Charges API integration on Web, iOS, and Android to use the Payment Intents API.
    4. Migrate your integration that saves cards on Customer objects.
    5. Test with 3D Secure test cards to ensure your upgraded integration handles 3D Secure authentication.

    Update your API version and your client library

    While the Payment Intents API works on all API versions, we recommend that you upgrade to the latest API version. If you decide to use an API version older than 2019-02-11, note the following two changes as you go through the code examples:

    • requires_source has been renamed to requires_payment_method
    • requires_source_action has been renamed to requires_action

    In addition, if you use one of our Client libraries, upgrade to the latest version of the library in order to use the Payment Intents API.

    Migrate your web integration

    An integration built with Stripe.js & Elements consists of the following steps:

    1. Collect payment details on the client side
    2. Attempt the payment on the server side
    3. Handle the server response on the client side

    Step 1: Collect payment details on the client side

    Use the createPaymentMethod function, which collects the payment information and submits it directly to Stripe.

    Before
    After
    stripe.createToken(
    // or stripe.createSource
      cardElement
    ).then(function(result) {
      if (result.error) {
        // Show error in payment form
      } else {
        // Send token.id to server
        fetch('/ajax/confirm_payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            token_id: result.token.id
          })
        }).then(function(result) {
          // Handle server response (see Step 3)
          result.json().then(function(json) {
            handleServerResponse(json);
          })
        });
      }
    });
    stripe.createPaymentMethod(
      'card',
      cardElement
    ).then(function(result) {
      if (result.error) {
        // Show error in payment form
      } else {
        // Send paymentMethod.id to server
        fetch('/ajax/confirm_payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            payment_method_id: result.paymentMethod.id
          })
        }).then(function(result) {
          // Handle server response (see Step 3)
          result.json().then(function(json) {
            handleServerResponse(json);
          })
        });
      }
    });
    Before
    After
    const {token} =
      await stripe.createToken(
        cardElement
      );
    
    // Send token.id to server
    const response = await fetch(
      '/ajax/confirm_payment', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          token_id: token.id
        })
      }
    );
    
    // Handle server response (see Step 3)
    handleServerResponse(await response.json());
    const {paymentMethod} =
      await stripe.createPaymentMethod(
        'card',
        cardElement
      );
    
    // Send paymentMethod.id to server
    const response = await fetch(
      '/ajax/confirm_payment', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          payment_method_id: paymentMethod.id
        })
      }
    );
    
    // Handle server response (see Step 3)
    await handleServerResponse(await response.json());

    Step 2: Attempt the payment on the server side

    In your existing integration, the final step is using tokenized payment information to create a charge on your server. With the Payment Intents API, this is sufficient if additional steps like 3D Secure authentication are not required. However, if some payments may require additional authentication, then additional code is required. Instead of creating a Charge, we’ll create a PaymentIntent.

    Before
    After
    curl https://api.stripe.com/v1/charges \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_ID}}" \
      -d amount=1099 \
      -d currency=usd
    
    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
    
    Before
    After
    charge = Stripe::Charge.create({
        source: data['token_id'],
        amount: 1099,
        currency: 'usd',
    })
    
    intent = Stripe::PaymentIntent.create({
        payment_method: data['payment_method_id'],
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true,
    })
    
    return generate_payment_response(intent)
    Before
    After
    charge = stripe.Charge.create(
      source=data['token_id'],
      amount=1099,
      currency='usd',
    )
    
    intent = stripe.PaymentIntent.create(
      payment_method=data['payment_method_id'],
      amount=1099,
      currency='usd',
      confirmation_method='manual',
      confirm=True,
    )
    
    return generate_payment_response(intent)
    
    Before
    After
    $charge = \Stripe\Charge::create([
        'source' => $json_obj->token_id,
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    $intent = \Stripe\PaymentIntent::create([
        'payment_method' => $json_obj->payment_method_id,
        'amount' => 1099,
        'currency' => 'usd',
        'confirmation_method' => 'manual',
        'confirm' => true,
    ]);
    
    generatePaymentResponse($intent);
    
    Before
    After
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("amount", 1099);
    chargeParams.put("currency", "usd");
    chargeParams.put("source", request.token_id);
    Charge.create(chargeParams);
    
    Map<String, Object> createPaymentIntentParams = new HashMap<String, Object>();
    createPaymentIntentParams.put("currency", "usd");
    createPaymentIntentParams.put("amount", 1099);
    createPaymentIntentParams.put("confirm", true);
    createPaymentIntentParams.put("confirmation_method", "manual");
    createPaymentIntentParams.put("payment_method", request.paymentMethodId);
    intent = PaymentIntent.create(createPaymentIntentParams);
    
    Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
    Before
    After
    (async () => {
      const charge = await stripe.charges.create({
        amount: 1099,
        currency: 'usd',
        source: request.body.token_id,
      });
    })();
    
    (async () => {
      const intent = await stripe.paymentIntents.create({
        payment_method: request.body.payment_method_id,
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });
    
      generate_payment_response(intent));
    })();
    
    Before
    After
    params := &stripe.ChargeParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    params.SetSource(*paymentRequest.TokenId)
    ch, _ := charge.New(params)
    
    params := &stripe.PaymentIntentParams{
      PaymentMethod: stripe.String(*paymentRequest.PaymentMethodId),
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      Confirm: stripe.Bool(true),
      ConfirmationMethod: stripe.String(string(stripe.PaymentIntentConfirmationMethodManual)),
    }
    
    intent, err = paymentintent.New(params)
    
    Before
    After
    var options = new ChargeCreateOptions
    {
        Amount = 999,
        Currency = "usd",
        Source = request.tokenId,
    };
    var service = new ChargeService();
    Charge charge = service.Create(options);
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        PaymentMethodId = request.PaymentMethodId,
        Amount = 1099,
        Currency = "usd",
        ConfirmationMethod = "manual",
        Confirm = true,
    };
    paymentIntent = service.Create(options);
    

    Check the PaymentIntent status with generate_payment_response. If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. If the payment is successful, then the status is set to succeeded and the payment is complete. If the payment failed, the status is set to requires_payment_method.

    You can see a full server implementation using generate_payment_response in our manual confirmation quickstart.

    # If status == 'requires_action' and
    # next_action.type == 'use_stripe_sdk'
    # -> Tell the client to handle the action
    #
    # If status == 'succeeded'
    # -> The payment didn't need any additional actions and completed!
    # -> Handle post-payment fulfillment
    def generate_payment_response(intent)
        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 completed!
            # Handle post-payment fulfillment
            [200, { success: true }.to_json]
        else
            # Invalid status
        end
    end
    
    def generate_payment_response(intent):
      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
    
    function generatePaymentResponse($intent) {
        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']);
        }
    }
    
    private Map<String, Object> generatePaymentResponse(Response response,
                                                        PaymentIntent intent) {
      response.type("application/json");
      Map<String, Object> responseData = new HashMap<>();
      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;
    }
    
    const generate_payment_response = (intent) => {
      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'
        }
      }
    };
    
    func generatePaymentResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) {
      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)
    
      }
    }
    
    private IActionResult generatePaymentResponse(PaymentIntent intent)
    {
        if (intent.Status == "requires_action" &&
            intent.NextAction.Type == "use_stripe_sdk")
        {
            // Tell the client to handle the action
            return Json(new
            {
                requires_action = true,
                payment_intent_client_secret = intent.ClientSecret
            });
        }
        else if (intent.Status == "succeeded")
        {
            // The payment didn’t need any additional actions and completed!
            // Handle post-payment fulfillment
            return Json(new { success = true });
        }
        else
        {
            // Invalid status
            return StatusCode(500, new { error = "Invalid PaymentIntent status" });
        }
    }
    

    Step 3: Handle the server response on the client side

    On the client side, handle the new case when additional action is required with stripe.handleCardAction. After it completes without error, the PaymentIntent now has a status of requires_confirmation. Send the ID of the PaymentIntent to the same server endpoint to confirm the payment again.

    Before
    After
    function handleServerResponse(response) {
      if (response.error) {
        // Show error on payment form
      } else {
        // Show success message
      }
    }
    
    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
        handleAction(response);
      } else {
        // Show success message
      }
    }
    
    function handleAction(response) {
      stripe.handleCardAction(
        response.payment_intent_client_secret
      ).then(function(result) {
        if (result.error) {
          // Show error in payment form
        } else {
          // The card action has been handled
          // The PaymentIntent can be confirmed again on the server
          fetch('/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: result.paymentIntent.id
            })
          }).then(function(confirmResult) {
            return confirmResult.json();
          }).then(handleServerResponse);
        }
      });
    }
    
    Before
    After
    function handleServerResponse(response) {
      if (response.error) {
        // Show error on payment form
      } else {
        // Show success message
      }
    }
    
    async 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
        await handleAction(response);
      } else {
        // Show success message
      }
    }
    
    async function handleAction(response) {
      const {
        error: errorAction,
        paymentIntent
      } = await stripe.handleCardAction(
        response.payment_intent_client_secret
      );
    
      if (errorAction) {
        // Show error from Stripe.js in payment form
      } else {
        const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: paymentIntent.id
            })
          }
        );
    
        // Step 3: handle server response
        handleServerResponse(await response.json());
      }
    }
    

    Now that you have migrated, use the test cards in the following section to verify your upgraded integration handles 3D Secure authentication.

    The Stripe.js Payment Request Button works with the Payment Intents API by using the PaymentMethod obtained in a traditional PaymentRequest integration and attaching it to a PaymentIntent.

    An integration built with the Payment Request API consists of the following steps:

    1. Collect payment details on the client side
    2. Attempt the payment on the server side
    3. Handle the server response on the client side

    Step 1: Collect payment details on the client side

    Listen for the PaymentRequest object’s paymentmethod event, which provides a paymentMethod and a callback function that you can use to indicate when the payment process is complete.

    Before
    After
    paymentRequest.on('token', function(ev) {
      // Send token to server
      fetch(
        '/ajax/confirm_payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            token_id: ev.token.id
          })
        }
      ).then(function(response) {
        // Handle server response (see Step 3)
        response.json().then(function(json) {
          handleServerResponse(json);
        });
      });
    });
    
    paymentRequest.on('paymentmethod', function(ev) {
      // Send paymentMethod to server
      fetch(
        '/ajax/confirm_payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            payment_method_id: ev.paymentMethod.id
          })
        }
      ).then(function(response) {
        // Handle server response (see Step 3)
        response.json().then(function(json) {
          handleServerResponse(json);
        });
      });
    });
    
    Before
    After
    paymentRequest.on('token',
      async ({token, complete}) => {
        // Send token to server
        const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              token_id: token.id
            })
          }
        );
    
        // Handle server response (see Step 3)
        handleServerResponse(await response.json(), complete);
    });
    
    paymentRequest.on('paymentmethod',
      async ({paymentMethod, complete}) => {
        // Send paymentMethod to server
        const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_method_id: paymentMethod.id
            })
          }
        );
    
        // Step 3: handle server response
        await handleServerResponse(await response.json(), complete);
    });
    

    Step 2: Attempt the payment on the server side

    In your existing integration, the final step is using tokenized payment information to create a charge on your server. With the Payment Intents API, this is sufficient if additional steps like 3D Secure authentication are not required. However, if some payments may require additional authentication, then additional code is required. Instead of creating a Charge, create a PaymentIntent.

    Before
    After
    curl https://api.stripe.com/v1/charges \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_ID}}" \
      -d amount=1099 \
      -d currency=usd
    
    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
    
    Before
    After
    charge = Stripe::Charge.create({
        source: data['token_id'],
        amount: 1099,
        currency: 'usd',
    })
    
    intent = Stripe::PaymentIntent.create({
        payment_method: data['payment_method_id'],
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true,
    })
    
    return generate_payment_response(intent)
    Before
    After
    charge = stripe.Charge.create(
      source=data['token_id'],
      amount=1099,
      currency='usd',
    )
    
    intent = stripe.PaymentIntent.create(
      payment_method=data['payment_method_id'],
      amount=1099,
      currency='usd',
      confirmation_method='manual',
      confirm=True,
    )
    
    return generate_payment_response(intent)
    
    Before
    After
    $charge = \Stripe\Charge::create([
        'source' => $json_obj->token_id,
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    $intent = \Stripe\PaymentIntent::create([
        'payment_method' => $json_obj->payment_method_id,
        'amount' => 1099,
        'currency' => 'usd',
        'confirmation_method' => 'manual',
        'confirm' => true,
    ]);
    
    generatePaymentResponse($intent);
    
    Before
    After
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("amount", 1099);
    chargeParams.put("currency", "usd");
    chargeParams.put("source", request.token_id);
    Charge.create(chargeParams);
    
    Map<String, Object> createPaymentIntentParams = new HashMap<String, Object>();
    createPaymentIntentParams.put("currency", "usd");
    createPaymentIntentParams.put("amount", 1099);
    createPaymentIntentParams.put("confirm", true);
    createPaymentIntentParams.put("confirmation_method", "manual");
    createPaymentIntentParams.put("payment_method", request.paymentMethodId);
    intent = PaymentIntent.create(createPaymentIntentParams);
    
    Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
    Before
    After
    (async () => {
      const charge = await stripe.charges.create({
        amount: 1099,
        currency: 'usd',
        source: request.body.token_id,
      });
    })();
    
    (async () => {
      const intent = await stripe.paymentIntents.create({
        payment_method: request.body.payment_method_id,
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });
    
      generate_payment_response(intent));
    })();
    
    Before
    After
    params := &stripe.ChargeParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    params.SetSource(*paymentRequest.TokenId)
    ch, _ := charge.New(params)
    
    params := &stripe.PaymentIntentParams{
      PaymentMethod: stripe.String(*paymentRequest.PaymentMethodId),
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      Confirm: stripe.Bool(true),
      ConfirmationMethod: stripe.String(string(stripe.PaymentIntentConfirmationMethodManual)),
    }
    
    intent, err = paymentintent.New(params)
    
    Before
    After
    var options = new ChargeCreateOptions
    {
        Amount = 999,
        Currency = "usd",
        Source = request.tokenId,
    };
    var service = new ChargeService();
    Charge charge = service.Create(options);
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        PaymentMethodId = request.PaymentMethodId,
        Amount = 1099,
        Currency = "usd",
        ConfirmationMethod = "manual",
        Confirm = true,
    };
    paymentIntent = service.Create(options);
    

    Check the PaymentIntent status with generate_payment_response. If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. If the payment is successful, then the status is set to succeeded and the payment is complete. If the payment failed, the status is set to requires_payment_method.

    You can see a full server implementation using generate_payment_response in our manual confirmation quickstart.

    # If status == 'requires_action' and
    # next_action.type == 'use_stripe_sdk'
    # -> Tell the client to handle the action
    #
    # If status == 'succeeded'
    # -> The payment didn't need any additional actions and completed!
    # -> Handle post-payment fulfillment
    def generate_payment_response(intent)
        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 completed!
            # Handle post-payment fulfillment
            [200, { success: true }.to_json]
        else
            # Invalid status
        end
    end
    
    def generate_payment_response(intent):
      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
    
    function generatePaymentResponse($intent) {
        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']);
        }
    }
    
    private Map<String, Object> generatePaymentResponse(Response response,
                                                        PaymentIntent intent) {
      response.type("application/json");
      Map<String, Object> responseData = new HashMap<>();
      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;
    }
    
    const generate_payment_response = (intent) => {
      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'
        }
      }
    };
    
    func generatePaymentResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) {
      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)
    
      }
    }
    
    private IActionResult generatePaymentResponse(PaymentIntent intent)
    {
        if (intent.Status == "requires_action" &&
            intent.NextAction.Type == "use_stripe_sdk")
        {
            // Tell the client to handle the action
            return Json(new
            {
                requires_action = true,
                payment_intent_client_secret = intent.ClientSecret
            });
        }
        else if (intent.Status == "succeeded")
        {
            // The payment didn’t need any additional actions and completed!
            // Handle post-payment fulfillment
            return Json(new { success = true });
        }
        else
        {
            // Invalid status
            return StatusCode(500, new { error = "Invalid PaymentIntent status" });
        }
    }
    

    Step 3: Handle the server response on the client side

    On the client side, handle the new case when additional action is required with stripe.handleCardAction. After it completes without error, the PaymentIntent now has a status of requires_confirmation. Send the ID of the PaymentIntent to the same server endpoint to confirm the payment again.

    Before
    After
    function handleServerResponse(response, complete) {
      if (response.error) {
        complete('fail');
      } else {
        complete('success');
      }
    }
    
    function handleServerResponse(response, complete) {
      if (response.error) {
        complete('fail');
      } else if (response.requires_action) {
        complete('success');
        handleAction(response);
      } else {
        complete('success');
      }
    }
    
    function handleAction(response) {
      stripe.handleCardAction(
        response.payment_intent_client_secret
      ).then(function(result) {
        if (result.error) {
          // Show error in payment form
        } else {
          // The card action has been handled
          // The PaymentIntent can be confirmed again on the server
          fetch('/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: result.paymentIntent.id
            })
          }).then(function(confirmResult) {
            return confirmResult.json();
          }).then(handleServerResponse);
        }
      });
    }
    
    Before
    After
    function handleServerResponse(response, complete) {
      if (response.error) {
        complete('fail');
      } else {
        complete('success');
      }
    }
    
    async function handleServerResponse(response, complete) {
      if (response.error) {
        complete('fail');
      } else if (response.requires_action) {
        complete('success');
        await handleAction(response);
      } else {
        complete('success');
      }
    }
    
    async function handleAction(response) {
      const {
        error: errorAction,
        paymentIntent
      } = await stripe.handleCardAction(
        response.payment_intent_client_secret
      );
    
      if (errorAction) {
        // Show error from Stripe.js in payment form
      } else {
        const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: paymentIntent.id
            })
          }
        );
    
        // Step 3: handle server response
        handleServerResponse(await response.json());
      }
    }
    

    Now that you have migrated, use the test cards in the following section to verify your upgraded integration handles 3D Secure authentication.

    If you’re using the legacy version of Checkout, upgrade to the new version of Checkout—the fastest way to accept payments with Stripe. With it, you can accept one-time payments and subscriptions. You can also use Checkout without using the Stripe API on your server. Follow the Checkout migration guide to migrate your existing integration.

    If you prefer to build your own checkout flow instead, switch to Elements. Assuming you have upgraded to Elements, you can build a Payment Intents API integration following the Payment Intents API migration guide for Elements.

    If you are preparing for Strong Customer Authentication (SCA) and need to migrate to the Payment Intents API without updating how you collect payment details, follow this guide.

    Step 1: Collect payment details on the client side

    To use the Payment Intents API with the legacy version of Checkout (Custom integration), first add the Stripe.js v3 script tag to your website and instantiate its Stripe object:

    <script src="https://js.stripe.com/v3/"></script>
    var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');

    Then, collect payment details in the same manner as your existing integration using the legacy version of Checkout.

    Before
    After
    StripeCheckout.configure({
      // ...configuration
      token: function(token) {
        // Send token.id to your server
        fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              token_id: token.id
            })
          }
        ).then(function(response) {
          // Handle server response (see Step 3)
          response.json().then(function(json) {
            handleServerResponse(json);
          });
        });
       },
     });
    
    Same as before
    Before
    After
    StripeCheckout.configure({
       // ...configuration
       token: async (token) => {
         // Send token.id to your server
         const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              token_id: token.id
            })
          }
        );
    
        // Handle server response (see Step 3)
        handleServerResponse(await response.json());
       },
     });
    
    Same as before

    Step 2: Attempt payment on the server side

    When attempting the payment on the server, pass the token when creating the PaymentIntent.

    Before
    After
    curl https://api.stripe.com/v1/charges \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_ID}}" \
      -d amount=1099 \
      -d currency=usd
    
    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d payment_method_data[type]=card \
      -d payment_method_data[card][token]="{{TOKEN_ID}}" \
      -d amount=1099 \
      -d currency=usd \
      -d confirmation_method=manual \
      -d confirm=true
    
    Before
    After
    charge = Stripe::Charge.create({
        source: data['token_id'],
        amount: 1099,
        currency: 'usd',
    })
    
    intent = Stripe::PaymentIntent.create({
        payment_method_data: {
            type: 'card',
            card: {token: data['token_id']},
        },
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true,
    })
    
    return generate_payment_response(intent)
    Before
    After
    charge = stripe.Charge.create(
      source=data['token_id'],
      amount=1099,
      currency='usd',
    )
    
    intent = stripe.PaymentIntent.create(
      payment_method_data={
        'type': 'card',
        'card':{'token':data['token_id']}
      },
      amount=1099,
      currency='usd',
      confirmation_method='manual',
      confirm=True,
    )
    
    return generate_payment_response(intent)
    
    Before
    After
    $charge = \Stripe\Charge::create([
        'source' => $json_obj->token_id,
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    $intent = \Stripe\PaymentIntent::create([
        'payment_method_data' => [
            'type' => 'card',
            'card' => ['token' => $json_obj->token_id],
        ],
        'amount' => 1099,
        'currency' => 'usd',
        'confirmation_method' => 'manual',
        'confirm' => true,
    ]);
    
    generatePaymentResponse($intent);
    
    Before
    After
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("amount", 1099);
    chargeParams.put("currency", "usd");
    chargeParams.put("source", request.token_id);
    Charge.create(chargeParams);
    
    Map<String, Object> createPaymentIntentParams = new HashMap<String, Object>();
    createPaymentIntentParams.put("currency", "usd");
    createPaymentIntentParams.put("amount", 1099);
    createPaymentIntentParams.put("confirm", true);
    createPaymentIntentParams.put("confirmation_method", "manual");
    Map<String, String> paymentMethodData = new HashMap<String, String>();
    paymentMethodData.put("type", "card");
    Map<String, String> cardData = new HashMap<String, String>();
    cardData.put("token", request.token_id);
    paymentMethodData.put("card", cardData);
    createPaymentIntentParams.put("payment_method_data", paymentMethodData);
    intent = PaymentIntent.create(createPaymentIntentParams);
    
    Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
    Before
    After
    (async () => {
      const charge = await stripe.charges.create({
        amount: 1099,
        currency: 'usd',
        source: request.body.token_id,
      });
    })();
    
    (async () => {
      const intent = await stripe.paymentIntents.create({
        payment_method_data: {
          type: 'card',
          card: { token: request.body.token_id }
        },
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });
    
      generate_payment_response(intent));
    })();
    
    Before
    After
    params := &stripe.ChargeParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    params.SetSource(*paymentRequest.TokenId)
    ch, _ := charge.New(params)
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      Confirm: stripe.Bool(true),
      ConfirmationMethod: stripe.String(string(stripe.PaymentIntentConfirmationMethodManual)),
    }
    params.AddExtra("payment_method_data[type]", "card")
    params.AddExtra("payment_method_data[card][token]", *paymentRequest.TokenId)
    
    intent, err = paymentintent.New(params)
    
    Before
    After
    var options = new ChargeCreateOptions
    {
        Amount = 999,
        Currency = "usd",
        Source = request.tokenId,
    };
    var service = new ChargeService();
    Charge charge = service.Create(options);
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
        Confirm = true,
        ConfirmationMethod = "automatic",
    };
    options.AddExtraParam("payment_method_data[type]", "card");
    options.AddExtraParam("payment_method_data[card][token]", request.tokenId);
    paymentIntent = service.Create(options);
    

    Check the PaymentIntent status with generate_payment_response. If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. If the payment is successful, then the status is set to succeeded and the payment is complete. If the payment failed, the status is set to requires_payment_method.

    You can see a full server implementation using generate_payment_response in our manual confirmation quickstart.

    # If status == 'requires_action' and
    # next_action.type == 'use_stripe_sdk'
    # -> Tell the client to handle the action
    #
    # If status == 'succeeded'
    # -> The payment didn't need any additional actions and completed!
    # -> Handle post-payment fulfillment
    def generate_payment_response(intent)
        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 completed!
            # Handle post-payment fulfillment
            [200, { success: true }.to_json]
        else
            # Invalid status
        end
    end
    
    def generate_payment_response(intent):
      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
    
    function generatePaymentResponse($intent) {
        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']);
        }
    }
    
    private Map<String, Object> generatePaymentResponse(Response response,
                                                        PaymentIntent intent) {
      response.type("application/json");
      Map<String, Object> responseData = new HashMap<>();
      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;
    }
    
    const generate_payment_response = (intent) => {
      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'
        }
      }
    };
    
    func generatePaymentResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) {
      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)
    
      }
    }
    
    private IActionResult generatePaymentResponse(PaymentIntent intent)
    {
        if (intent.Status == "requires_action" &&
            intent.NextAction.Type == "use_stripe_sdk")
        {
            // Tell the client to handle the action
            return Json(new
            {
                requires_action = true,
                payment_intent_client_secret = intent.ClientSecret
            });
        }
        else if (intent.Status == "succeeded")
        {
            // The payment didn’t need any additional actions and completed!
            // Handle post-payment fulfillment
            return Json(new { success = true });
        }
        else
        {
            // Invalid status
            return StatusCode(500, new { error = "Invalid PaymentIntent status" });
        }
    }
    

    Step 3: Handle the server response on the client side

    On the client side, handle the new case when additional action is required with stripe.handleCardAction. After it completes without error, the PaymentIntent now has a status of requires_confirmation. Send the ID of the PaymentIntent to the same server endpoint to confirm the payment again.

    Before
    After
    function handleServerResponse(response) {
      if (response.error) {
        // Show error on payment form
      } else {
        // Show success message
      }
    }
    
    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
        handleAction(response);
      } else {
        // Show success message
      }
    }
    
    function handleAction(response) {
      stripe.handleCardAction(
        response.payment_intent_client_secret
      ).then(function(result) {
        if (result.error) {
          // Show error in payment form
        } else {
          // The card action has been handled
          // The PaymentIntent can be confirmed again on the server
          fetch('/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: result.paymentIntent.id
            })
          }).then(function(confirmResult) {
            return confirmResult.json();
          }).then(handleServerResponse);
        }
      });
    }
    
    Before
    After
    function handleServerResponse(response) {
      if (response.error) {
        // Show error on payment form
      } else {
        // Show success message
      }
    }
    
    async 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
        await handleAction(response);
      } else {
        // Show success message
      }
    }
    
    async function handleAction(response) {
      const {
        error: errorAction,
        paymentIntent
      } = await stripe.handleCardAction(
        response.payment_intent_client_secret
      );
    
      if (errorAction) {
        // Show error from Stripe.js in payment form
      } else {
        const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: paymentIntent.id
            })
          }
        );
    
        // Step 3: handle server response
        handleServerResponse(await response.json());
      }
    }
    

    Now that you have migrated, use the test cards in the following section to verify your upgraded integration handles 3D Secure authentication.

    In order to upgrade an existing Stripe.js v2 integration to use the Payment Intents API, first update your integration to collect payment information with Elements. Elements provides pre-built UI components and offers simple PCI compliance with SAQ A reporting.

    After you have upgraded to Elements, you can build a Payment Intents API integration following the Payment Intents API migration guide for Elements.

    If you are preparing for Strong Customer Authentication (SCA) and need to migrate to the Payment Intents API without updating how you collect payment details, follow this guide.

    Step 1: Collect payment details on the client side

    To use the Payment Intents API with the Stripe.js v2, first add the Stripe.js v3 script tag to your website and instantiate its Stripe object:

    <script src="https://js.stripe.com/v3/"></script>
    var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');

    Then, collect payment details in the same manner as your existing integration.

    Before
    After
    Stripe.card.createToken({
      number: $('.card-number').val(),
      cvc: $('.card-cvc').val(),
      exp_month: $('.card-expiry-month').val(),
      exp_year: $('.card-expiry-year').val(),
      address_zip: $('.address_zip').val()
    }, function(status, token) {
      // Send token.id to your server
      fetch(
        '/ajax/confirm_payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            token_id: token.id
          })
        }
      ).then(function(response) {
        // Handle server response (see Step 3)
        response.json().then(function(json) {
          handleServerResponse(json);
        });
      });
    });
    
    Same as before
    Before
    After
    Stripe.card.createToken({
      number: $('.card-number').val(),
      cvc: $('.card-cvc').val(),
      exp_month: $('.card-expiry-month').val(),
      exp_year: $('.card-expiry-year').val(),
      address_zip: $('.address_zip').val()
    }, (status, token) => {
      // Send token.id to your server
      const response = await fetch(
        '/ajax/confirm_payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            token_id: token.id
          })
        }
      );
    
      // Handle server response (see Step 3)
      handleServerResponse(await response.json());
    });
    
    Same as before

    Step 2: Attempt payment on the server side

    When attempting the payment on the server, pass the token when creating the PaymentIntent.

    Before
    After
    curl https://api.stripe.com/v1/charges \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_ID}}" \
      -d amount=1099 \
      -d currency=usd
    
    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d payment_method_data[type]=card \
      -d payment_method_data[card][token]="{{TOKEN_ID}}" \
      -d amount=1099 \
      -d currency=usd \
      -d confirmation_method=manual \
      -d confirm=true
    
    Before
    After
    charge = Stripe::Charge.create({
        source: data['token_id'],
        amount: 1099,
        currency: 'usd',
    })
    
    intent = Stripe::PaymentIntent.create({
        payment_method_data: {
            type: 'card',
            card: {token: data['token_id']},
        },
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true,
    })
    
    return generate_payment_response(intent)
    Before
    After
    charge = stripe.Charge.create(
      source=data['token_id'],
      amount=1099,
      currency='usd',
    )
    
    intent = stripe.PaymentIntent.create(
      payment_method_data={
        'type': 'card',
        'card':{'token':data['token_id']}
      },
      amount=1099,
      currency='usd',
      confirmation_method='manual',
      confirm=True,
    )
    
    return generate_payment_response(intent)
    
    Before
    After
    $charge = \Stripe\Charge::create([
        'source' => $json_obj->token_id,
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    $intent = \Stripe\PaymentIntent::create([
        'payment_method_data' => [
            'type' => 'card',
            'card' => ['token' => $json_obj->token_id],
        ],
        'amount' => 1099,
        'currency' => 'usd',
        'confirmation_method' => 'manual',
        'confirm' => true,
    ]);
    
    generatePaymentResponse($intent);
    
    Before
    After
    Map<String, Object> chargeParams = new HashMap<String, Object>();
    chargeParams.put("amount", 1099);
    chargeParams.put("currency", "usd");
    chargeParams.put("source", request.token_id);
    Charge.create(chargeParams);
    
    Map<String, Object> createPaymentIntentParams = new HashMap<String, Object>();
    createPaymentIntentParams.put("currency", "usd");
    createPaymentIntentParams.put("amount", 1099);
    createPaymentIntentParams.put("confirm", true);
    createPaymentIntentParams.put("confirmation_method", "manual");
    Map<String, String> paymentMethodData = new HashMap<String, String>();
    paymentMethodData.put("type", "card");
    Map<String, String> cardData = new HashMap<String, String>();
    cardData.put("token", request.token_id);
    paymentMethodData.put("card", cardData);
    createPaymentIntentParams.put("payment_method_data", paymentMethodData);
    intent = PaymentIntent.create(createPaymentIntentParams);
    
    Map<String, Object> responseData = generatePaymentResponse(response, intent);
    
    Before
    After
    (async () => {
      const charge = await stripe.charges.create({
        amount: 1099,
        currency: 'usd',
        source: request.body.token_id,
      });
    })();
    
    (async () => {
      const intent = await stripe.paymentIntents.create({
        payment_method_data: {
          type: 'card',
          card: { token: request.body.token_id }
        },
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });
    
      generate_payment_response(intent));
    })();
    
    Before
    After
    params := &stripe.ChargeParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    params.SetSource(*paymentRequest.TokenId)
    ch, _ := charge.New(params)
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      Confirm: stripe.Bool(true),
      ConfirmationMethod: stripe.String(string(stripe.PaymentIntentConfirmationMethodManual)),
    }
    params.AddExtra("payment_method_data[type]", "card")
    params.AddExtra("payment_method_data[card][token]", *paymentRequest.TokenId)
    
    intent, err = paymentintent.New(params)
    
    Before
    After
    var options = new ChargeCreateOptions
    {
        Amount = 999,
        Currency = "usd",
        Source = request.tokenId,
    };
    var service = new ChargeService();
    Charge charge = service.Create(options);
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1099,
        Currency = "usd",
        Confirm = true,
        ConfirmationMethod = "automatic",
    };
    options.AddExtraParam("payment_method_data[type]", "card");
    options.AddExtraParam("payment_method_data[card][token]", request.tokenId);
    paymentIntent = service.Create(options);
    

    Check the PaymentIntent status with generate_payment_response. If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_action. If the payment is successful, then the status is set to succeeded and the payment is complete. If the payment failed, the status is set to requires_payment_method.

    You can see a full server implementation using generate_payment_response in our manual confirmation quickstart.

    # If status == 'requires_action' and
    # next_action.type == 'use_stripe_sdk'
    # -> Tell the client to handle the action
    #
    # If status == 'succeeded'
    # -> The payment didn't need any additional actions and completed!
    # -> Handle post-payment fulfillment
    def generate_payment_response(intent)
        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 completed!
            # Handle post-payment fulfillment
            [200, { success: true }.to_json]
        else
            # Invalid status
        end
    end
    
    def generate_payment_response(intent):
      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
    
    function generatePaymentResponse($intent) {
        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']);
        }
    }
    
    private Map<String, Object> generatePaymentResponse(Response response,
                                                        PaymentIntent intent) {
      response.type("application/json");
      Map<String, Object> responseData = new HashMap<>();
      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;
    }
    
    const generate_payment_response = (intent) => {
      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'
        }
      }
    };
    
    func generatePaymentResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) {
      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)
    
      }
    }
    
    private IActionResult generatePaymentResponse(PaymentIntent intent)
    {
        if (intent.Status == "requires_action" &&
            intent.NextAction.Type == "use_stripe_sdk")
        {
            // Tell the client to handle the action
            return Json(new
            {
                requires_action = true,
                payment_intent_client_secret = intent.ClientSecret
            });
        }
        else if (intent.Status == "succeeded")
        {
            // The payment didn’t need any additional actions and completed!
            // Handle post-payment fulfillment
            return Json(new { success = true });
        }
        else
        {
            // Invalid status
            return StatusCode(500, new { error = "Invalid PaymentIntent status" });
        }
    }
    

    Step 3: Handle the server response on the client side

    On the client side, handle the new case when additional action is required with stripe.handleCardAction. After it completes without error, the PaymentIntent now has a status of requires_confirmation. Send the ID of the PaymentIntent to the same server endpoint to confirm the payment again.

    Before
    After
    function handleServerResponse(response) {
      if (response.error) {
        // Show error on payment form
      } else {
        // Show success message
      }
    }
    
    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
        handleAction(response);
      } else {
        // Show success message
      }
    }
    
    function handleAction(response) {
      stripe.handleCardAction(
        response.payment_intent_client_secret
      ).then(function(result) {
        if (result.error) {
          // Show error in payment form
        } else {
          // The card action has been handled
          // The PaymentIntent can be confirmed again on the server
          fetch('/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: result.paymentIntent.id
            })
          }).then(function(confirmResult) {
            return confirmResult.json();
          }).then(handleServerResponse);
        }
      });
    }
    
    Before
    After
    function handleServerResponse(response) {
      if (response.error) {
        // Show error on payment form
      } else {
        // Show success message
      }
    }
    
    async 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
        await handleAction(response);
      } else {
        // Show success message
      }
    }
    
    async function handleAction(response) {
      const {
        error: errorAction,
        paymentIntent
      } = await stripe.handleCardAction(
        response.payment_intent_client_secret
      );
    
      if (errorAction) {
        // Show error from Stripe.js in payment form
      } else {
        const response = await fetch(
          '/ajax/confirm_payment', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              payment_intent_id: paymentIntent.id
            })
          }
        );
    
        // Step 3: handle server response
        handleServerResponse(await response.json());
      }
    }
    

    Now that you have migrated, use the test cards in the following section to verify your upgraded integration handles 3D Secure authentication.

    Test the integration

    It’s important to thoroughly test your Payment Intents API integration to make sure you’re correctly handling cards that require additional authentication and cards that don’t to ensure the smoothest checkout experience for your users. Use the test cards in test mode to validate your integration when 3D Secure is: required, required and the payment is declined, and not required. Use these card numbers with any expiration date in the future and any three digit CVC code.

    Card Number 3D Secure usage Description
    4000000000003220 Required This test card requires 3D Secure 2 on all transactions and will trigger 3D Secure 2 in test mode.
    4000008400001629 Required This test card requires 3D Secure 2 but payments will be declined with a card_declined failure code after authentication.
    4000000000003055 Supported This test card supports but does not require 3D Secure 2 and will not require additional authentication steps in test mode.

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

    Migrate your integration that saves cards on Customer objects

    For saving cards in the checkout flow, there are two differences between your existing integration and a Payment Intents API integration with manual confirmation.

    On the client side, use the createPaymentMethod function instead of createToken or createSource.

    Before
    After
    stripe.createToken(
    // or stripe.createSource
      cardElement
    ).then(function(result) {
      if (result.error) {
        // Display result.error.message in UI
      } else {
        // Send result.token to server
      }
    });
    
    stripe.createPaymentMethod(
      'card',
      cardElement
    ).then(function(result) {
      if (result.error) {
        // Display result.error.message in UI
      } else {
        // The PaymentMethod has successfully
        // been created
      }
    });
    
    Before
    After
    const {token, error} =
      await stripe.createToken(
      // or stripe.createSource
        cardElement
      );
    if (error) {
      // Display error.message in UI
    } else {
      // Send token to server
    }
    
    const {paymentMethod, error} =
      await stripe.createPaymentMethod(
        'card',
        cardElement
      );
    if (error) {
      // Display error.message in UI
    } else {
      // The PaymentMethod has successfully
      // been created
    }
    

    On the server side, use the save_payment_method parameter on the PaymentIntent to attach the provided payment method to the customer. Your existing integration requires a separate API call to save the payment method—this is no longer necessary.

    Before
    After
    curl https://api.stripe.com/v1/customers/{{CUSTOMER_ID}}/sources \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_OR_SOURCE}}"
    

    Completed in the next step

    Before
    After
    card = Stripe::Customer.create_source(
        '{{CUSTOMER_ID}}',
        {
            source: '{{TOKEN_OR_SOURCE}}',
        }
    )

    Completed in the next step

    Before
    After
    stripe.Customer.create_source(
      '{{CUSTOMER_ID}}',
      source='{{TOKEN_OR_SOURCE}}'
    )

    Completed in the next step

    Before
    After
    $card = \Stripe\Customer::createSource(
        '{{CUSTOMER_ID}}',
        [
            'source' => '{{TOKEN_OR_SOURCE}}',
        ]
    );

    Completed in the next step

    Before
    After
    Customer customer = Customer.retrieve("{{CUSTOMER_ID}}");
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("source", "{{TOKEN_OR_SOURCE}}");
    customer.getSources().create(params);

    Completed in the next step

    Before
    After
    (async () => {
      const card = await stripe.customers.createSource(
        '{{CUSTOMER_ID}}',
        {source: '{{TOKEN_OR_SOURCE}}'}
      );
    })();

    Completed in the next step

    Before
    After
    params := &stripe.CardParams{
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      Token: stripe.String("{{TOKEN_OR_SOURCE}}"),
    }
    c, err := card.New(params)

    Completed in the next step

    Before
    After
    var options = new CardCreateOptions
    {
        SourceToken = "{{TOKEN_OR_SOURCE}}"
    };
    var service = new CardService();
    var card = service.Create("{{CUSTOMER_ID}}", options);

    Completed in the next step

    Before
    After
    curl https://api.stripe.com/v1/charges \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_OR_SOURCE}}" \
      -d amount=1099 \
      -d currency=usd \
      -d customer="{{CUSTOMER_ID}}"
    
    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d payment_method="{{PAYMENT_METHOD_ID}}" \
      -d save_payment_method=true \
      -d customer="{{CUSTOMER_ID}}" \
      -d amount=1099 \
      -d currency=usd \
      -d confirmation_method=manual \
      -d confirm=true
    
    Before
    After
    charge = Stripe::Charge.create(
        source: '{{TOKEN_OR_SOURCE}}',
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
    )
    
    Stripe::PaymentIntent.create({
        payment_method: '{{PAYMENT_METHOD_ID}}',
        save_payment_method: true,
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true,
    })
    
    Before
    After
    charge = stripe.Charge.create(
      source='{{TOKEN_OR_SOURCE}}',
      customer='{{CUSTOMER_ID}}',
      amount=1099,
      currency='usd',
    )
    
    stripe.PaymentIntent.create(
      payment_method='{{PAYMENT_METHOD_ID}}',
      save_payment_method=True,
      customer='{{CUSTOMER_ID}}',
      amount=1099,
      currency='usd',
      confirmation_method='manual',
      confirm=True,
    )
    
    Before
    After
    $charge = \Stripe\Charge::create([
        'source' => '{{TOKEN_OR_SOURCE}}',
        'customer' => '{{CUSTOMER_ID}}',
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    $intent = \Stripe\PaymentIntent::create([
        'payment_method' => '{{PAYMENT_METHOD_ID}}',
        'save_payment_method' => true,
        'customer' => '{{CUSTOMER_ID}}',
        'amount' => 1099,
        'currency' => 'usd',
        'confirmation_method' => 'manual',
        'confirm' => true,
    ]);
    
    Before
    After
    Map<String, Object> params = new HashMap<>();
    params.put("amount", 1099);
    params.put("currency", "usd");
    params.put("customer", "{{CUSTOMER_ID}}");
    params.put("source", "{{TOKEN_OR_SOURCE}}");
    Charge charge = Charge.create(params);
    
    Map<String, Object> params = new HashMap<>();
    params.put("payment_method", "{{PAYMENT_METHOD_ID}}");
    params.put("save_payment_method", true);
    params.put("customer", "{{CUSTOMER_ID}}");
    params.put("amount", 1099);
    params.put("currency", "usd");
    params.put("confirmation_method", "manual");
    params.put("confirm", true);
    
    PaymentIntent.create(params);
    Before
    After
    (async () => {
      const charge = await stripe.charges.create({
        source: '{{TOKEN_OR_SOURCE}}',
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
      });
    })();
    (async () => {
      const intent = await stripe.paymentIntents.create({
        payment_method: '{{PAYMENT_METHOD_ID}}',
        save_payment_methd: true,
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });
    })();
    Before
    After
    params := &stripe.ChargeParams{
        Customer: stripe.String("{{CUSTOMER_ID}}"),
        Amount: stripe.Int64(1099),
        Currency: stripe.String(string(string(stripe.CurrencyUSD))),
    }
    params.SetSource("{{TOKEN_OR_SOURCE}}")
    ch, _ := charge.New(params)
    
    params := &stripe.PaymentIntentParams{
        PaymentMethod: stripe.String("{{PAYMENT_METHOD_ID}}"),
        SavePaymentMethod: stripe.Bool(true),
        Customer: stripe.String("{{CUSTOMER_ID}}"),
        Amount: stripe.Int64(1099),
        Currency: stripe.String(string(string(stripe.CurrencyUSD))),
        ConfirmationMethod: stripe.String(string(stripe.PaymentIntentConfirmationMethodManual)),
        Confirm: stripe.Bool(true),
    }
    intent, err = paymentintent.New(params)
    
    Before
    After
    var options = new ChargeCreateOptions
    {
        CardId = "{{TOKEN_OR_SOURCE}}",
        CustomerId = "{{CUSTOMER_ID}}",
        Amount = 1099,
        Currency = "usd",
    };
    var service = new ChargeService();
    Charge charge = service.Create(options);
    
    var options = new PaymentIntentCreateOptions
    {
        PaymentMethodId = "{{PAYMENT_METHOD_ID}}",
        SavePaymentMethod = true,
        CustomerId = "{{CUSTOMER_ID}}",
        Amount = 1099,
        Currency = "usd",
        ConfirmationMethod = "manual",
        Confirm = true,
    };
    
    var service = new PaymentIntentService();
    var paymentIntent = service.Create(options);
    

    For saving cards outside the checkout flow, there are two differences between your existing integration and a Payment Methods API integration.

    On the client side, use the createPaymentMethod function instead of createToken or createSource.

    Before
    After
    stripe.createToken(
    // or stripe.createSource
      cardElement
    ).then(function(token) {
      // Send token to server
    });
    stripe.createPaymentMethod(
      'card',
      cardElement
    ).then(function(result) {
      if (result.error) {
        // Display error.message in your UI.
      } else {
        // The PaymentMethod has successfully been created
      }
    });
    Before
    After
    const {token} =
      await stripe.createToken(
      // or stripe.createSource
        cardElement
      );
    const {paymentMethod} =
      await stripe.createPaymentMethod(
        'card',
        cardElement
      );

    On the server side, attach the payment method to the Customer object using the Payment Methods API instead of the Customers API.

    Before
    After
    curl https://api.stripe.com/v1/customers/{{CUSTOMER_ID}}/sources \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{TOKEN_FROM_STEP_1}}"
    
    curl https://api.stripe.com/v1/payment_methods/{{PAYMENT_METHOD_ID}}/attach \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d customer="{{CUSTOMER_ID}}"
    
    Before
    After
    card = Stripe::Customer.create_source(
        '{{CUSTOMER_ID}}',
        {
            source: '{{TOKEN_FROM_STEP_1}}',
        }
    )
    Stripe::PaymentMethod.attach(
        '{{PAYMENT_METHOD_ID}}',
        {
            customer: '{{CUSTOMER_ID}}',
        }
    )
    Before
    After
    stripe.Customer.create_source(
      '{{CUSTOMER_ID}}',
      source='{{TOKEN_FROM_STEP_1}}'
    )
    payment_method = stripe.PaymentMethod.attach(
      '{{PAYMENT_METHOD_ID}}',
      customer='{{CUSTOMER_ID}}'
    )
    Before
    After
    $card = \Stripe\Customer::createSource(
        '{{CUSTOMER_ID}}',
        [
            'source' => '{{TOKEN_FROM_STEP_1}}',
        ]
    );
    $payment_method = \Stripe\PaymentMethod::retrieve('{{PAYMENT_METHOD_ID}}');
    $payment_method->attach(['customer' => '{{CUSTOMER_ID}}']);
    Before
    After
    Customer customer = Customer.retrieve("{{CUSTOMER_ID}}");
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("source", "{{TOKEN_FROM_STEP_1}}");
    customer.getSources().create(params);
    PaymentMethod paymentMethod = PaymentMethod.retrieve("{{PAYMENT_METHOD_ID}}");
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("customer", "{{CUSTOMER_ID}}");
    paymentMethod.attach(params);
    Before
    After
    stripe.customers.createSource(
      '{{CUSTOMER_ID}}',
      {
        source: '{{TOKEN_FROM_STEP_1}}',
      },
      function(err, card) {
        // asynchronously called
      }
    );
    stripe.paymentMethods.attach('{{PAYMENT_METHOD_ID}}', {customer: '{{CUSTOMER_ID}}'}, function(err, paymentMethod) {
      // asynchronously called
    });
    Before
    After
    params := &stripe.CardParams{
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      Token: stripe.String("{{TOKEN_FROM_STEP_1}}"),
    }
    c, err := card.New(params)
    params := &stripe.PaymentMethodAttachParams{
      Customer: "{{CUSTOMER_ID}}",
    }
    p, err := paymentmethod.Attach("{{PAYMENT_METHOD_ID}}", params)
    Before
    After
    var options = new CardCreateOptions
    {
        SourceToken = "{{TOKEN_FROM_STEP_1}}"
    };
    var service = new CardService();
    var card = service.Create("{{CUSTOMER_ID}}", options);
    var service = new PaymentMethodService();
    var options = new PaymentMethodAttachOptions
    {
        Customer = "{{CUSTOMER_ID}}",
    };
    var paymentMethod = service.Attach("{{PAYMENT_METHOD_ID}}", options);

    When paying with a previously saved payment method, you must specify both the ID of the Customer and the ID of the previously saved Card, Source, or PaymentMethod. Previously, the default source on the customer is used if one was not provided. You must now explicitly pass in the desired payment method.

    Before
    After
    curl https://api.stripe.com/v1/charges \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d source="{{PAYMENT_METHOD_ID}}" \
      -d amount=1099 \
      -d currency=usd \
      -d customer="{{CUSTOMER_ID}}"
    
    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 customer="{{CUSTOMER_ID}}" \
      -d confirmation_method=manual \
      -d confirm=true
    
    Before
    After
    charge = Stripe::Charge.create({
        source: '{{SOURCE_ID}}',
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
    })
    
    Stripe::PaymentIntent.create({
        # An existing Card or Source ID
        # can also be used
        payment_method: '{{PAYMENT_METHOD_ID}}'',
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true,
    })
    
    Before
    After
    charge = stripe.Charge.create(
      source='{{SOURCE_ID}}',
      customer='{{CUSTOMER_ID}}',
      amount=1099,
      currency='usd',
    )
    
    stripe.PaymentIntent.create(
      amount=1099,
      payment_method='{{PAYMENT_METHOD_ID}}',
      customer='{{CUSTOMER_ID}}',
      currency='usd',
      confirmation_method='manual',
      confirm=True,
    )
    
    Before
    After
    $charge = \Stripe\Charge::create([
        'source' => '{{SOURCE_ID}}',
        'customer' => '{{CUSTOMER_ID}}',
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    $intent = \Stripe\PaymentIntent::create([
        'amount' => 1099,
        'payment_method' => '{{PAYMENT_METHOD_ID}}',
        'currency' => 'usd',
        'confirmation_method' => 'manual',
        'confirm' => true,
        'customer' => '{{CUSTOMER_ID}}'
    ]);
    
    Before
    After
    Map<String, Object> params = new HashMap<>();
    params.put("amount", 1099);
    params.put("currency", "usd");
    params.put("customer", "{{CUSTOMER_ID}}");
    params.put("source", "{{SOURCE_ID}}");
    Charge charge = Charge.create(params);
    
    /* New support for typed param builder in stripe-java version 9.1.0 */
    PaymentIntentCreateParams createParams = PaymentIntentCreateParams.builder()
      .setAmount(1099)
      .setCurrency("usd")
      .setConfirm(true)
      .setPaymentMethod("{{PAYMENT_METHOD_ID}}")
      .setConfirmationMethod(PaymentIntentCreateParams.ConfirmationMethod.MANUAL)
      .build();
    intent = PaymentIntent.create(createParams);
    
    Before
    After
    (async () => {
      const charge = await stripe.charges.create({
        source: '{{SOURCE_ID}}',
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
      })
    })();
    
    (async () => {
      const intent = await stripe.paymentIntents.create({
        payment_method: '{{PAYMENT_METHOD_ID}}',
        customer: '{{CUSTOMER_ID}}',
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });
    })();
    
    Before
    After
    params := &stripe.ChargeParams{
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(string(stripe.CurrencyUSD))),
    }
    params.SetSource("{{SOURCE_ID}}")
    ch, _ := charge.New(params)
    
    params := &stripe.PaymentIntentParams{
      PaymentMethod: stripe.String("{{PAYMENT_METHOD_ID}}"),
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      Amount: stripe.Int64(1099),
      Currency: stripe.String(string(string(stripe.CurrencyUSD))),
      ConfirmationMethod: stripe.String(string(stripe.PaymentIntentConfirmationMethodManual)),
      Confirm: stripe.Bool(true),
    }
    intent, err = paymentintent.New(params)
    
    Before
    After
    var options = new ChargeCreateOptions
    {
        Source = "{{SOURCE_ID}}",
        CustomerId = "{{CUSTOMER_ID}}",
        Amount = 1099,
        Currency = "usd",
    };
    var service = new ChargeService();
    Charge charge = service.Create(options);
    
    var paymentIntentService = new PaymentIntentService();
    
    var createOptions = new PaymentIntentCreateOptions
    {
        PaymentMethodId = "{{PAYMENT_METHOD_ID}}",
        CustomerId = "{{CUSTOMER_ID}}",
        Amount = 1099,
        Currency = "usd",
        ConfirmationMethod = "manual",
        Confirm = true,
    };
    var paymentIntent = paymentIntentService.Create(createOptions);
    

    Access saved payment methods

    To display the customer’s previously saved Cards, Sources, and PaymentMethods, list the payment methods instead of reading the sources property of the customer object. This is required because new PaymentMethods added to a customer will not be duplicated in the sources property of the customer object.

    Before
    After
    customer.sources
    curl "https://api.stripe.com/v1/payment_methods?customer={{CUSTOMER_ID}}&type=card" \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc:
    
    Before
    After
    customer.sources
    Stripe::PaymentMethod.list({
        type: 'card',
        customer: '{{CUSTOMER_ID}}',
    })
    Before
    After
    customer.sources
    stripe.PaymentMethod.list(customer='{{CUSTOMER_ID}}', type='card')
    Before
    After
    customer->sources
    \Stripe\PaymentMethod::all(['customer' => '{{CUSTOMER_ID}}', 'type' => 'card']);
    Before
    After
    customer.sources
    Map<String, Object> params = new HashMap<String, Object>();
    paymentmethodParams.put("customer", "{{CUSTOMER_ID}}");
    paymentmethodParams.put("type", "card");
    
    PaymentMethod.list(params);
    
    Before
    After
    customer.sources
    const paymentMethods = await stripe.paymentMethods.list(
      { customer: '{{CUSTOMER_ID}}', type: 'card' }
    );
    
    Before
    After
    customer.sources
    params := &stripe.PaymentMethodListParams{
      Customer: stripe.String("{{CUSTOMER_ID}}"),
      Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
    }
    i := paymentmethod.List(params)
    for i.Next() {
      p := i.PaymentMethod()
    }
    
    
    Before
    After
    customer.Sources
    var service = new PaymentMethodService();
    var options = new PaymentMethodListOptions
    {
        Customer = "{{CUSTOMER_ID}}",
        Limit = 3,
        Type = "card"
    };
    var paymentMethods = service.List(options);
    

    Next steps

    Now you have all the information you need to begin migrating an existing Stripe integration to the Payment Intents API with manual confirmation. To learn more, continue reading:

    Questions?

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

    Was this page helpful? Yes No

    Send

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

    On this page