Sign in
An image of the Stripe logo
Create account
Sign in
Home
Payments
Business operations
Financial services
Developer tools
Security
All products
Home
Payments
Business operations
Home
Payments
Business operations
Financial services
Developer tools
Support

Migrating to Payment Intents
Server-side confirmation

Learn how to migrate your existing cards and Charges API integration.

Use this migration path if your integration waits for the response from the client before finalizing the payment on the server. Server-side confirmation has the following limitations:

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

If you don’t mind these limitations, feel free to use this migration path. Otherwise, use the standard migration path, which automatically handles any actions required for collecting payment.

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 regulatory test cards to ensure your upgraded integration handles authentication correctly.

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 one-time payment flows

“One-time payments” are payment flows that collect card details to be charged once and immediately (for example, e-commerce purchases, one-off donations). For payment flows that save card details to be charged later or on a recurring basis, follow the migration guide on saving cards.

Pick the migration path that matches your current Stripe 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({ type: 'card', 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); }) }); } });

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
Terminal
curl https://api.stripe.com/v1/charges \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "source"="{{TOKEN_ID}}" \ -d "amount"=1099 \ -d "currency"="usd"
Terminal
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"

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.

See a full server implementation using generate_payment_response in the manual confirmation quickstart.

Terminal
# 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

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

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

Migrate your integration that saves cards on Customer objects

You will need to change your integration to let Stripe know how you intend to use your customers’ saved cards so we can optimize for the right exemptions. SCA may also require that the card is authenticated before being stored.

For saving cards in the checkout flow after a payment is made, there are a few 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({ type: 'card', card: cardElement, }).then(function(result) { if (result.error) { // Display result.error.message in UI } else { // The PaymentMethod has successfully // been created } });

On the server side, use the setup_future_usage parameter on the PaymentIntent to save the card for future use. You can pass a Customer ID to the PaymentIntent to save the payment method immediately upon creation, or attach the payment method to a Customer at a later time.

Before
After
Terminal
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
Terminal
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}}"
Terminal
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
: \ -d "payment_method"="{{PAYMENT_METHOD_ID}}" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "amount"=1099 \ -d "currency"="usd" \ -d "confirmation_method"="manual" \ -d "confirm"="true" \ -d "setup_future_usage"="off_session"

setup_future_usage tells Stripe how you intend to use the saved card and allows for optimizing the authentication process for future payments. Learn more about setup_future_usage in the Payment Intents API manual.

If the card requires authentication before it can be charged and saved to a customer, it will have a status of requires_action. If the PaymentIntent requires action, call handleCardAction on the client with the PaymentIntent’s client_secret. handleCardAction handles displaying the UI to the customer to authenticate the card. Once authentication is complete, you can confirm the PaymentIntent again on the server to finalize the payment. Learn more about handling require card actions in the one-time payments guide.

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
Terminal
customer.sources
Terminal
curl https://api.stripe.com/v1/payment_methods?customer={{CUSTOMER_ID}}&type=card \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
:

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.

NumberAuthenticationDescription
Required on setup or first transactionThis 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.
RequiredThis test card requires authentication on all transactions.
RequiredThis test card requires authentication, but payments will be declined with an insufficient_funds failure code after successful authentication.
SupportedThis 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

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:

  • Migrating to Payment Intents with Automatic Confirmation
  • Payment Intents on iOS
  • Payment Intents on Android
Was this page helpful?
Questions? Contact us.
View developer tutorials on YouTube.
Check out our product changelog.
Powered by Markdoc
You can unsubscribe at any time. Read our privacy policy.
On this page
Update your API version and your client library
Migrate your one-time payment flows
Migrate your integration that saves cards on Customer objects
Access saved payment methods
Test the integration
Next steps
Stripe Shell
Test mode
▗▄ ▄▟█ █▀▀ ▗▟████▙▖ ██████ ███▗▟█ ███ ███▗▟██▙▖ ▗▟█████▙▖ ███▖ ▀▀ ███ ███▀▀▀ ███ ███▀ ███ ███ ███ ▝▜████▙▖ ███ ███ ███ ███ ███ █████████ ▄▄ ▝███ ███ ▄ ███ ███ ███▄ ███ ███ ▄▄ ▝▜████▛▘ ▝▜███▛ ███ ███ ███▝▜██▛▘ ▝▜█████▛▘ ███ ▀▘
Welcome to the Stripe Shell! Stripe Shell is a browser-based shell with the Stripe CLI pre-installed. Login to Stripe docs and press Control + Backtick on your keyboard to start managing your Stripe resources in test mode. - View supported commands: - Find webhook events: - Listen for webhook events: - Call Stripe APIs: stripe [api resource] [operation] (e.g. )
The Stripe Shell is best experienced on desktop.
$