Payments
Older APIs
Charges
Migrate to the new APIs

Migrating to Payment Intents

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

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

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

  1. Register your intent to collect payment on the server side
  2. Collect payment details on the client side
  3. Initiate creation of the payment
  4. Fulfill the customer’s order on the server side

Step 1: Register intent to collect payment on the server side

Create a PaymentIntent on your server and make it accessible on the client side.

Before
After

Not possible before

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d currency=usd
Before
After

Not possible before

Stripe::PaymentIntent.create({ amount: 1099, currency: 'usd', })
Before
After

Not possible before

stripe.PaymentIntent.create( amount=1099, currency='usd' )
Before
After

Not possible before

$intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'usd', ]);
Before
After

Not possible before

Map<String, Object> params = new HashMap<String, Object>(); params.put("amount", 1099); params.put("currency", "usd"); PaymentIntent.create(params);
Before
After

Not possible before

const paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: 'usd', });
Before
After

Not possible before

params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyUSD)), } intent, err := paymentintent.New(params)
Before
After

Not possible before

var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "usd", }; var paymentIntent = service.Create(options);

Step 2: Collect payment details on the client side

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

Before
After
stripe.createToken( cardElement ).then(function(token) { // Send token to server });
stripe.confirmCardPayment( INTENT_SECRET_FROM_STEP_1, { payment_method: {card: cardElement} } ).then(function(result) { if (result.error) { // Display error.message in your UI. } else { // The payment has succeeded // Display a success message } });
Before
After
const {token} = await stripe.createToken( cardElement ); // Send token to server
const {paymentIntent, error} = await stripe.confirmCardPayment( INTENT_SECRET_FROM_STEP_1, { payment_method: {card: cardElement} } ); if (error) { // Display error.message in your UI. } else { // The payment has succeeded // Display a success message }

Step 3: Initiate creation of the payment

In your existing integration, the final step is using tokenized payment information to create a charge on your server. This is no longer necessary, as the confirmCardPayment function—called in the previous step—initiates creation of the charge.

Before
After
curl https://api.stripe.com/v1/charges \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d source="{{FROM_PREVIOUS_STEP}}" \ -d amount=1099 \ -d currency=usd

Completed in previous step

Before
After
charge = Stripe::Charge.create( source: '{{FROM_PREVIOUS_STEP}}', amount: 1099, currency: 'usd', )

Completed in previous step

Before
After
charge = stripe.Charge.create( source='{{FROM_PREVIOUS_STEP}}', amount=1099, currency='usd', )

Completed in previous step

Before
After
$charge = \Stripe\Charge::create([ 'source' => '{{FROM_PREVIOUS_STEP}}', 'amount' => 1099, 'currency' => 'usd', ]);

Completed in previous step

Before
After
Map<String, Object> params = new HashMap<>(); params.put("source", "{{FROM_PREVIOUS_STEP}}"); params.put("amount", 1099); params.put("currency", "usd"); Charge charge = Charge.create(params);

Completed in previous step

Before
After
const charge = await stripe.charges.create({ source: '{{FROM_PREVIOUS_STEP}}', amount: 1099, currency: 'usd', });

Completed in previous step

Before
After
params := &stripe.ChargeParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyUSD)), } params.SetSource("{{FROM_PREVIOUS_STEP}}") ch, _ := charge.New(params)

Completed in previous step

Before
After
var options = new ChargeCreateOptions { Source = "{{FROM_PREVIOUS_STEP}}", Amount = 1099, Currency = "usd", }; var service = new ChargeService(); Charge charge = service.Create(options);

Completed in previous step

Step 4: Fulfill the customer’s order

With automatic confirmation, the charge is created for you asynchronously based on customer action on the client side, so you must monitor webhooks to determine when the payment completes successfully. To perform steps like order fulfillment after a customer’s payment is successful, implement support for webhooks and monitor the payment_intent.succeeded event.

Before
After

If charge succeeds, fulfill.

Subscribe to the payment_intent.succeeded webhook and fulfill in the webhook handler.

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 token obtained in a traditional PaymentRequest integration and attaching it to a PaymentIntent.

An integration built with PaymentRequest consists of the following steps:

  1. Register your intent to collect payment on the server side
  2. Collect payment details on the client side
  3. Initiate creation of the payment
  4. Fulfill the customer’s order on the server side

Step 1: Register intent to collect payment on the server side

Create a PaymentIntent on your server and make it accessible on the client side.

Before
After

Not possible before

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d currency=usd
Before
After

Not possible before

Stripe::PaymentIntent.create({ amount: 1099, currency: 'usd', })
Before
After

Not possible before

stripe.PaymentIntent.create( amount=1099, currency='usd' )
Before
After

Not possible before

$intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'usd', ]);
Before
After

Not possible before

Map<String, Object> params = new HashMap<String, Object>(); params.put("amount", 1099); params.put("currency", "usd"); PaymentIntent.create(params);
Before
After

Not possible before

const paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: 'usd', });
Before
After

Not possible before

params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyUSD)), } intent, err := paymentintent.New(params)
Before
After

Not possible before

var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "usd", }; var paymentIntent = service.Create(options);

Step 2: Collect payment details on the client side

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

To adapt this integration to support the Payment Intents API, use the confirmCardPayment function and pass it the token ID instead of sending the token to your server. When the promise returned by the confirmCardPayment function resolves, invoke the completion callback.

Before
After
paymentRequest.on( 'token', function(ev) { // Send the token to the server // Complete with 'fail' and show error if error: ev.complete('fail'); // Complete with 'success' if success ev.complete('success'); });
paymentRequest.on( 'paymentmethod', function(ev) { // First, confirm the PaymentIntent without handling potential // next actions to see if there are any payment errors. stripe.confirmCardPayment( INTENT_SECRET_FROM_STEP_1, {payment_method: ev.paymentMethod.id}, {handleActions: false} ).then(function(confirmResult) { if (confirmResult.error) { // Complete with 'fail' and show error: ev.complete('fail'); } else { // Otherwise, close the Payment Request // modal and call confirmCardPayment again // to complete any potential actions // required. ev.complete('success'); stripe.confirmCardPayment(INTENT_SECRET_FROM_STEP_1).then(function(result) { if (result.error) { // Handle payment error, if any. } }); } }); } );
Before
After
paymentRequest.on( 'token', ({token, complete}) => { // Send the token to the server // Complete with 'fail' and show error if error: complete('fail'); // Complete with 'success' if success complete('success'); });
paymentRequest.on( 'paymentmethod', async ({paymentMethod, complete}) => { // First, confirm the PaymentIntent without handling potential // next actions to see if there are any payment errors. const {error: confirmError} = await stripe.confirmCardPayment( INTENT_SECRET_FROM_STEP_1, {payment_method: ev.paymentMethod.id}, {handleActions: false} ); if (confirmError) { // Complete with 'fail' and show error: complete('fail'); } else { // Otherwise, close the Payment Request // modal and call confirmCardPayment again // to complete any potential actions // required. complete('success'); const {error, paymentIntent} = await stripe.confirmCardPayment( INTENT_SECRET_FROM_STEP_1, ); // Handle payment error, if any. } } );

Step 3: Initiate creation of the payment

In your existing integration, the final step is using tokenized payment information to create a charge on your server. This is no longer necessary, as the confirmCardPayment function—called in the previous step—initiates creation of the charge.

Before
After
curl https://api.stripe.com/v1/charges \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d source="{{FROM_PREVIOUS_STEP}}" \ -d amount=1099 \ -d currency=usd

Completed in previous step

Before
After
charge = Stripe::Charge.create( source: '{{FROM_PREVIOUS_STEP}}', amount: 1099, currency: 'usd', )

Completed in previous step

Before
After
charge = stripe.Charge.create( source='{{FROM_PREVIOUS_STEP}}', amount=1099, currency='usd', )

Completed in previous step

Before
After
$charge = \Stripe\Charge::create([ 'source' => '{{FROM_PREVIOUS_STEP}}', 'amount' => 1099, 'currency' => 'usd', ]);

Completed in previous step

Before
After
Map<String, Object> params = new HashMap<>(); params.put("source", "{{FROM_PREVIOUS_STEP}}"); params.put("amount", 1099); params.put("currency", "usd"); Charge charge = Charge.create(params);

Completed in previous step

Before
After
const charge = await stripe.charges.create({ source: '{{FROM_PREVIOUS_STEP}}', amount: 1099, currency: 'usd', });

Completed in previous step

Before
After
params := &stripe.ChargeParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyUSD)), } params.SetSource("{{FROM_PREVIOUS_STEP}}") ch, _ := charge.New(params)

Completed in previous step

Before
After
var options = new ChargeCreateOptions { Source = "{{FROM_PREVIOUS_STEP}}", Amount = 1099, Currency = "usd", }; var service = new ChargeService(); Charge charge = service.Create(options);

Completed in previous step

Step 4: Fulfill the customer’s order

With automatic confirmation, the charge is created for you asynchronously based on customer action on the client side, so you must monitor webhooks to determine when the payment completes successfully. To perform steps like order fulfillment after a customer’s payment is successful, implement support for webhooks and monitor the payment_intent.succeeded event.

Before
After

If charge succeeds, fulfill.

Subscribe to the payment_intent.succeeded webhook and fulfill in the webhook handler.

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 using the Elements migration guide.

To support Strong Customer Authentication (SCA) without updating how you collect payment details, follow the manual confirmation migration guide.

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 prebuilt 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 using the Elements migration guide.

To support Strong Customer Authentication (SCA) without updating how you collect payment details, follow the manual confirmation migration guide.

Migrate your integration that saves cards on Customer objects

A Payment Intents API integration that collects card information in the checkout flow consists of the following steps:

  1. Register your intent to collect payment on the server side
  2. Collect payment details on the client side
  3. Initiate creation of the payment
  4. Fulfill the customer’s order on the server side

Step 1: Register intent to collect payment on the server side

Create a PaymentIntent on your server. Set setup_future_usage to off_session if you primarily intend to charge users when they are outside of your application, or on_session if you plan to charge them in the application. If you plan to use the card for both on and off session payments use off_session. Providing the setup_future_usage parameter along with a Customer ID will save the resulting PaymentMethod to that Customer after the PaymentIntent has been confirmed and any required actions from the customer are complete. Next, make the PaymentIntent accessible on the client side.

Before
After

Not possible before

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d setup_future_usage=off_session \ -d amount=1099 \ -d currency=usd
Before
After

Not possible before

Stripe::PaymentIntent.create({ setup_future_usage: 'off_session', amount: 1099, currency: 'usd', })
Before
After

Not possible before

stripe.PaymentIntent.create( setup_future_usage='off_session', amount=1099, currency='usd' )
Before
After

Not possible before

$intent = \Stripe\PaymentIntent::create([ 'setup_future_usage' => 'off_session', 'amount' => 1099, 'currency' => 'usd', ]);
Before
After

Not possible before

Map<String, Object> params = new HashMap<String, Object>(); params.put("setup_future_usage", "off_session"); params.put("amount", 1099); params.put("currency", "usd"); PaymentIntent.create(params);
Before
After

Not possible before

const paymentIntent = await stripe.paymentIntents.create({ setup_future_usage: 'off_session', amount: 1099, currency: 'usd', });
Before
After

Not possible before

params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyUSD)), SetupFutureUsage: stripe.String(string(stripe.PaymentIntentSetupFutureUsageOffSession)), } intent, err := paymentintent.New(params)
Before
After

Not possible before

var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, SetupFutureUsage = "off_session", Currency = "usd", }; var paymentIntent = service.Create(options);

Step 2: Collect payment details on the client side

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

Before
After
stripe.createToken( // or stripe.createSource cardElement ).then(function(token) { // Send token to server });
stripe.confirmCardPayment( '{{INTENT_SECRET_FROM_STEP_1}}', { payment_method: {card: cardElement}, } ).then(function(result) { if (result.error) { // Display error.message in your UI. } else { // The payment has succeeded // Display a success message } });
Before
After
const {token} = await stripe.createToken( // or stripe.createSource cardElement );
const {paymentIntent, error} = await stripe.confirmCardPayment( '{{INTENT_SECRET_FROM_STEP_1}}', { payment_method: {card: cardElement}, } );

Finally, attach the payment method (paymentIntent.payment_method) to the customer.

Before
After
curl https://api.stripe.com/v1/customers/{{CUSTOMER_ID}}/sources \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d source="{{TOKEN_OR_SOURCE}}"
curl https://api.stripe.com/v1/payment_method/{{PAYMENT_METHOD_ID}}/attach \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d customer="{{CUSTOMER_ID}}"
Before
After
card = Stripe::Customer.create_source( '{{CUSTOMER_ID}}', { source: '{{TOKEN_OR_SOURCE}}', } )
payment_method = Stripe::PaymentMethod.attach( '{{PAYMENT_METHOD_ID}}', { customer: '{{CUSTOMER_ID}}', } )
Before
After
stripe.Customer.create_source( '{{CUSTOMER_ID}}', source='{{TOKEN_OR_SOURCE}}', )
payment_method = stripe.PaymentMethod.attach( '{{PAYMENT_METHOD_ID}}', customer='{{CUSTOMER_ID}}', )
Before
After
$card = \Stripe\Customer::createSource( '{{CUSTOMER_ID}}', [ 'source' => '{{TOKEN_OR_SOURCE}}', ] );
$payment_method = \Stripe\PaymentMethod::retrieve('{{PAYMENT_METHOD_ID}}'); $payment_method->attach(['customer' => '{{CUSTOMER_ID}}']);
Before
After
CustomerRetrieveParams customerParams = CustomerRetrieveParams.builder() .addExpand("sources") .build(); Customer customer = Customer.retrieve( "{{CUSTOMER_ID}}, customerParams, null ); Map<String, Object> params = new HashMap<String, Object>(); params.put("source", "{{TOKEN_OR_SOURCE}}"); customer.getSources().create(params);
PaymentMethod paymentMethod = PaymentMethod.retrieve("{{PAYMENT_METHOD_ID}}"); PaymentMethodAttachParams params = PaymentMethodAttachParams.builder() .setCustomer("{{CUSTOMER_ID}}") .build(); paymentMethod.attach(params);
Before
After
const card = await stripe.customers.createSource( '{{CUSTOMER_ID}}', { source: '{{TOKEN_OR_SOURCE}}', } );
const card = await stripe.paymentMethods.attach( '{{PAYMENT_METHOD_ID}}', { customer: '{{CUSTOMER_ID}}', } );
Before
After
params := &stripe.CardParams{ Customer: stripe.String("{{CUSTOMER_ID}}"), Token: stripe.String("{{TOKEN_OR_SOURCE}}"), } c, _ := card.New(params)
params := &stripe.PaymentMethodAttachParams{ Customer: "{{CUSTOMER_ID}}", } p, _ := paymentmethod.Attach("{{PAYMENT_METHOD_ID}}", params)
Before
After
var options = new CardCreateOptions { Source = "{{TOKEN_OR_SOURCE}}", }; 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);

Step 3: Initiate creation of the payment

In your existing integration, the final step is using tokenized payment information to create a charge on your server. This is no longer necessary, as the confirmCardPayment function—called in the previous step—initiates creation of the charge.

Before
After
curl https://api.stripe.com/v1/charges \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d source="{{FROM_PREVIOUS_STEP}}" \ -d customer="{{CUSTOMER_ID}}" \ -d amount=1099 \ -d currency=usd

Completed in previous step

Before
After
charge = Stripe::Charge.create( source: '{{FROM_PREVIOUS_STEP}}', customer: '{{CUSTOMER_ID}}', amount: 1099, currency: 'usd', )

Completed in previous step

Before
After
charge = stripe.Charge.create( source='{{FROM_PREVIOUS_STEP}}', customer='{{CUSTOMER_ID}}', amount=1099, currency='usd', )

Completed in previous step

Before
After
$charge = \Stripe\Charge::create([ 'source' => '{{FROM_PREVIOUS_STEP}}', 'customer' => '{{CUSTOMER_ID}}', 'amount' => 1099, 'currency' => 'usd', ]);

Completed in previous step

Before
After
Map<String, Object> params = new HashMap<>(); params.put("source", "{{FROM_PREVIOUS_STEP}}"); params.put("customer", "{{CUSTOMER_ID}}"); params.put("amount", 1099); params.put("currency", "usd"); Charge charge = Charge.create(params);

Completed in previous step

Before
After
const charge = await stripe.charges.create({ source: '{{FROM_PREVIOUS_STEP}}', customer: '{{CUSTOMER_ID}}', amount: 1099, currency: 'usd', });

Completed in previous step

Before
After
params := &stripe.ChargeParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyUSD)), } params.SetSource("{{FROM_PREVIOUS_STEP}}") params.SetCustomer("{{CUSTOMER_ID}}") ch, _ := charge.New(params)

Completed in previous step

Before
After
var options = new ChargeCreateOptions { Source = "{{FROM_PREVIOUS_STEP}}", Customer = "{{CUSTOMER_ID}}", Amount = 1099, Currency = "usd", }; var service = new ChargeService(); Charge charge = service.Create(options);

Completed in previous step

Step 4: Fulfill the customer’s order

With automatic confirmation, the charge is created for you asynchronously based on customer action on the client side, so you must monitor webhooks to determine when the payment completes successfully. To perform steps like order fulfillment after a customer’s payment is successful, implement support for webhooks and monitor the payment_intent.succeeded event.

Before
After

If charge succeeds, fulfill.

Subscribe to the payment_intent.succeeded webhook and fulfill in the webhook handler.

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

There are a couple of changes you will need to make to your existing integration that saves card details outside of a checkout flow. First, create a SetupIntent on your server. A SetupIntent authenticates the card without making an initial payment.

Before
After

No server code previously required

curl https://api.stripe.com/v1/setup_intents/ \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -X POST
Before
After

No server code previously required

setup_intent = Stripe::SetupIntent.create
Before
After

No server code previously required

setup_intent = stripe.SetupIntent.create()
Before
After

No server code previously required

$setupIntent = \Stripe\SetupIntent::create();
Before
After

No server code previously required

SetupIntent.create();
Before
After

No server code previously required

const intent = await stripe.setupIntents.create();
Before
After

No server code previously required

intent, err = setupintent.New(params)
Before
After

No server code previously required

var service = new SetupIntentService(); var setupIntent = service.Create(null);

Pass the SetupIntent’s client secret to your client and use the confirmCardSetup function instead of createToken or createSource after your customer enters their card details.

If required by regulation, confirmCardSetup will prompt the user to authenticate the card.

Before
After
stripe.createToken( // or stripe.createSource cardElement, { name: cardholderName.value, } ).then(function(token) { // Send token to server });
stripe.confirmCardSetup( clientSecret, { payment_method: { card: cardElement, billing_details: {name: cardholderName.value} } } ).then(function(result) { if (result.error) { // Display error.message in your UI. } else { // The SetupIntent was successful! } });
Before
After
const {token} = await stripe.createToken( // or stripe.createSource cardElement, { name: cardholderName.value, } );
const {setupIntent, error} = await stripe.confirmCardSetup( clientSecret, { payment_method: { card: cardElement, billing_details: {name: cardholderName.value} } } ); if (error) { // Display error.message in your UI. } else { // The SetupIntent was successful! }

When confirmCardSetup succeeds, setupIntent.payment_method will contain a PaymentMethod that you can attach to a Customer.

Pass the ID of that PaymentMethod to your server, and 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
CustomerRetrieveParams customerParams = CustomerRetrieveParams.builder() .addExpand("sources") .build(); Customer customer = Customer.retrieve( "{{CUSTOMER_ID}}, customerParams, null ); 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 { Source = "{{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.

If the customer is off-session, you must flag the payment as an off-session payment. See Charging Saved Cards for more information.

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

Test the integration

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

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

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

Next steps

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

Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.