Accept card payments without webhooks
Learn how to confirm a card payment on your server and handle card authentication requests.
Caution
Stripe recommends using the newer Payment Element instead of the Card Element. It allows you to accept multiple payment methods with a single Element. Learn more about when to use the Card Element and Payment Element.
For a wider range of support and future proofing, use the standard integration for asynchronous payments.
This integration waits for the returned response from the client and finalizes a payment on the server, without using webhooks or processing offline events. While it may seem simpler, this integration is difficult to scale as your business grows and has several limitations:
- Only supports cards—You’ll have to write more code to support ACH and popular regional payment methods separately.
- Double-charge risk—By synchronously creating a new PaymentIntent each time your customer attempts to pay, you risk accidentally double-charging your customer. Be sure to follow best practices.
- Extra trip to client—Cards with 3D Secure or those that are subject to regulations such as Strong Customer Authentication require extra steps on the client.
Keep these limitations in mind if you decide to use this integration. Otherwise, use the standard integration.
Set up Stripe
First, you need a Stripe account. Register now.
Use our official libraries for access to the Stripe API from your application:
Collect card detailsClient-side
Collect card information on the client with Stripe.js and Stripe Elements. Elements is a set of prebuilt UI components for collecting and validating card number, postal code, and expiration date.
A Stripe Element contains an iframe that securely sends the payment information to Stripe over an HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.
You can test your integration without using HTTPS. Enable it when you’re ready to accept live payments.
Submit the PaymentMethod to your serverClient-side
If the PaymentMethod was created successfully, send its ID to your server.
const stripePaymentMethodHandler = async (result) => { if (result.error) { // Show error in payment form } else { // Otherwise send paymentMethod.id to your server (see Step 4) const res = await fetch('/pay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_method_id: result.paymentMethod.id, }), }) const paymentResponse = await res.json(); // Handle server response (see Step 4) handleServerResponse(paymentResponse); } }
Create a PaymentIntentServer-side
Set up an endpoint on your server to receive the request. This endpoint will also be used later to handle cards that require an extra step of authentication.
Create a new PaymentIntent with the ID of the PaymentMethod created on your client. You can confirm the PaymentIntent by setting the confirm property to true when the PaymentIntent is created or by calling confirm after creation. Separate authorization and capture is also supported for card payments.
If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to requires_
. If the payment failed, the status is set back to requires_
and you should show an error to your user. If the payment doesn’t require any additional authentication then a charge is created and the PaymentIntent status is set to succeeded
.
Note
On versions of the API before 2019-02-11, requires_
appears as requires_
and requires_
appears as requires_
.
If you want to save the card to reuse later, create a Customer to store the PaymentMethod and pass the following additional parameters when creating the PaymentIntent:
- customer. Set to the ID of the Customer.
- setup_future_usage. Set to
off_
to tell Stripe that you plan to reuse this PaymentMethod for off-session payments when your customer is not present. Setting this property saves the PaymentMethod to the Customer after the PaymentIntent is confirmed and any required actions from the user are complete. See the code sample on saving cards after a payment for more details.session
Handle any next actionsClient-side
Write code to handle situations that require your customer to intervene. A payment normally succeeds after you confirm it on the server in step 4. However, when the PaymentIntent requires additional action from the customer, such as authenticating with 3D Secure, this code comes into play.
Use stripe.handleCardAction to trigger the UI for handling customer action. If authentication succeeds, the PaymentIntent has a status of requires_
. Confirm the PaymentIntent again on your server to finish the payment.
While testing, use a test card number that requires authentication (for example, ) to force this flow. Using a card that doesn’t require authentication (for example, ) skips this part of the flow and completes at step 4.
const handleServerResponse = async (response) => { if (response.error) { // Show error from server on payment form } else if (response.requires_action) { // Use Stripe.js to handle the required card action const { error: errorAction, paymentIntent } = await stripe.handleCardAction(response.payment_intent_client_secret); if (errorAction) { // Show error from Stripe.js in payment form } else { // The card action has been handled // The PaymentIntent can be confirmed again on the server const serverResponse = await fetch('/pay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_intent_id: paymentIntent.id }) }); handleServerResponse(await serverResponse.json()); } } else { // Show success message } }
Note
stripe.
may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.
Confirm the PaymentIntent againServer-side
This code is only executed when a payment requires additional authentication—just like the handling in the previous step. The code itself isn’t optional because any payment could require this extra step.
Using the same endpoint you set up above, confirm the PaymentIntent again to finalize the payment and fulfill the order. Make sure this confirmation happens within one hour of the payment attempt. Otherwise, the payment fails and transitions back to requires_
.
Test the integration
Several test cards are available for you to use in test mode to make sure this integration is ready. Use them with any CVC and an expiration date in the future.
Number | Description |
---|---|
Succeeds and immediately processes the payment. | |
Requires authentication. Stripe triggers a modal asking for the customer to authenticate. | |
Always fails with a decline code of insufficient_ . |
For the full list of test cards see our guide on testing.