Payments
Payment Intents
Payment status updates

Payment status updates

Monitor and verify payment status, so that you can respond to successful and failing payments.

PaymentIntents update in response to actions taken by the customer or payment method. Your integration can inspect the PaymentIntent to determine the status of the payment process, so that you can take business actions or respond to states that require further intervention.

Check PaymentIntent status on the client

When completing a payment on the client with the confirmCardPayment function, you can inspect the returned PaymentIntent to determine its current status:

stripe.confirmCardPayment(clientSecret).then(function(response) { if (response.error) { // Handle error here } else if (response.paymentIntent && response.paymentIntent.status === 'succeeded') { // Handle successful payment here } });
(async () => { const {paymentIntent, error} = await stripe.confirmCardPayment(clientSecret); if (error) { // Handle error here } else if (paymentIntent && paymentIntent.status === 'succeeded') { // Handle successful payment here } })();

The following are the possible outcomes of using the confirmCardPayment function:

Event What Happened Expected Integration
Resolves with a PaymentIntent Customer completed payment on your checkout page Inform customer that their payment was successful
Resolves with an error Customer’s payment failed on your checkout page Display error message and prompt your customer to attempt payment again

The promise returned by confirmCardPayment resolves when the payment process has either completed or failed with an error. When it completes successfully and returns a PaymentIntent, the status is always either succeeded or requires_capture. When the payment requires an additional step like authentication, the promise doesn’t resolve until that step is either complete or has timed out.

To check the status of a PaymentIntent without using the confirmCardPayment function, retrieve it independently by using the retrievePaymentIntent function and passing in the client secret:

stripe.retrievePaymentIntent(clientSecret).then(function(response) { if (response.error) { // Handle error here } else if (response.paymentIntent && response.paymentIntent.status === 'succeeded') { // Handle successful payment here } });
(async () => { const {paymentIntent, error} = await stripe.retrievePaymentIntent(clientSecret); if (error) { // Handle error here } else if (paymentIntent && paymentIntent.status === 'succeeded') { // Handle successful payment here } })();

Monitor a PaymentIntent with webhooks

Stripe can send webhook events to your server to notify you when the status of a PaymentIntent changes. This is useful for purposes like determining when to fulfill the goods and services purchased by the customer.

Your integration should not attempt to handle order fulfillment on the client side because it is possible for customers to leave the page after payment is complete but before the fulfillment process initiates. Instead, we strongly recommend using webhooks to monitor the payment_intent.succeeded event and handle its completion asynchronously instead of attempting to initiate fulfillment on the client side.

To handle a webhook event, create a route on your server and configure a corresponding webhook endpoint in the Dashboard. Stripe sends the payment_intent.succeeded event when payment is successful and the payment_intent.payment_failed event when payment isn’t successful.

The webhook payload includes the PaymentIntent object. The following example shows how to handle both events:

require 'sinatra' require 'stripe' post '/webhook' do payload = request.body.read sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, endpoint_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature status 400 return end case event['type'] when 'payment_intent.succeeded' intent = event['data']['object'] puts "Succeeded:", intent['id'] # Fulfill the customer's purchase when 'payment_intent.payment_failed' intent = event['data']['object'] error_message = intent['last_payment_error'] && intent['last_payment_error']['message'] puts "Failed:", intent['id'], error_message # Notify the customer that payment failed end status 200 end
# You can find your endpoint's secret in your webhook settings endpoint_secret = 'whsec_...' @app.route("/webhook", methods=['POST']) def webhook(): payload = request.get_data() sig_header = request.headers.get('STRIPE_SIGNATURE') event = None try: event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except ValueError as e: # invalid payload return "Invalid payload", 400 except stripe.error.SignatureVerificationError as e: # invalid signature return "Invalid signature", 400 event_dict = event.to_dict() if event_dict['type'] == "payment_intent.succeeded": intent = event_dict['data']['object'] print "Succeeded: ", intent['id'] # Fulfill the customer's purchase elif event_dict['type'] == "payment_intent.payment_failed": intent = event_dict['data']['object'] error_message = intent['last_payment_error']['message'] if intent.get('last_payment_error') else None print "Failed: ", intent['id'], error_message # Notify the customer that payment failed return "OK", 200
// You can find your endpoint's secret in your webhook settings $endpoint_secret = 'whsec_...'; $payload = @file_get_contents('php://input'); $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; $event = null; try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); } catch(\UnexpectedValueException $e) { // Invalid payload http_response_code(400); exit(); } catch(\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature http_response_code(400); exit(); } if ($event->type == "payment_intent.succeeded") { $intent = $event->data->object; printf("Succeeded: %s", $intent->id); http_response_code(200); exit(); } elseif ($event->type == "payment_intent.payment_failed") { $intent = $event->data->object; $error_message = $intent->last_payment_error ? $intent->last_payment_error->message : ""; printf("Failed: %s, %s", $intent->id, $error_message); http_response_code(200); exit(); }
import com.google.gson.JsonSyntaxException; import com.stripe.exception.SignatureVerificationException; import com.stripe.model.PaymentIntent; import com.stripe.model.Event; import com.stripe.net.Webhook; import static spark.Spark.post; public class StripeJavaQuickStart { public static void main(String[] args) { String endpointSecret = "whsec_..."; post("/webhook", (request, response) -> { String payload = request.body(); String sigHeader = request.headers("Stripe-Signature"); Event event = null; try { event = Webhook.constructEvent( payload, sigHeader, endpointSecret ); } catch (JsonSyntaxException e) { // Invalid payload response.status(400); return "Invalid payload"; } catch (SignatureVerificationException e) { // Invalid signature response.status(400); return "Invalid signature"; } PaymentIntent intent = (PaymentIntent) event .getDataObjectDeserializer() .getObject() .get(); switch(event.getType()) { case "payment_intent.succeeded": System.out.println("Succeeded: " + intent.getId()); break; // Fulfil the customer's purchase case "payment_intent.payment_failed": System.out.println("Failed: " + intent.getId()); break; // Notify the customer that payment failed default: // Handle other event types break; } response.status(200); return "OK"; }); } }
app.use(require('body-parser').text({type: '*/*'})); const endpointSecret = 'whsec_...'; app.post('/webhook', function(request, response) { const sig = request.headers['stripe-signature']; const body = request.body; let event = null; try { event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret); } catch (err) { // invalid signature response.status(400).end(); return; } let intent = null; switch (event['type']) { case 'payment_intent.succeeded': intent = event.data.object; console.log("Succeeded:", intent.id); break; case 'payment_intent.payment_failed': intent = event.data.object; const message = intent.last_payment_error && intent.last_payment_error.message; console.log('Failed:', intent.id, message); break; } response.sendStatus(200); });
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" stripe "github.com/stripe/stripe-go/v71" webhook "github.com/stripe/stripe-go/v71/webhook" ) func main() { http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) { const MaxBodyBytes = int64(65536) req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes) body, err := ioutil.ReadAll(req.Body) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusServiceUnavailable) return } // Pass the request body & Stripe-Signature header to ConstructEvent, along with the webhook signing key // You can find your endpoint's secret in your webhook settings endpointSecret := "whsec_..."; event, err := webhook.ConstructEvent(body, req.Header.Get("Stripe-Signature"), endpointSecret) if err != nil { fmt.Fprintf(os.Stderr, "Error verifying webhook signature: %v\n", err) w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature return } if event.Type == "payment_intent.succeeded" { var paymentIntent stripe.PaymentIntent err := json.Unmarshal(event.Data.Raw, &paymentIntent) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } fmt.Sprintf("Succeeded: %v\n", paymentIntent) // Fulfil the customer's purchase } else if event.Type == "payment_intent.payment_failed" { var paymentIntent stripe.PaymentIntent err := json.Unmarshal(event.Data.Raw, &paymentIntent) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } fmt.Sprintf("Failed: %v, %v\n", paymentIntent, paymentIntent.LastPaymentError) // Notify the customer that payment failed } w.WriteHeader(http.StatusOK) }) http.ListenAndServe(":3000", nil) }
using System; using System.IO; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Stripe; namespace StripeExampleApi.Controllers { [Route("webhook")] [ApiController] public class WebhookController : Controller { private readonly ILogger<WebhookController> _logger; public WebhookController(ILogger<WebhookController> logger) { _logger = logger; } const string secret = "whsec_..."; [HttpPost] public ActionResult Post() { try { var json = new StreamReader(HttpContext.Request.Body).ReadToEnd(); var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], secret); PaymentIntent intent = null; switch (stripeEvent.Type) { case "payment_intent.succeeded": intent = (PaymentIntent)stripeEvent.Data.Object; _logger.LogInformation("Succeeded: {ID}", intent.Id); // Fulfil the customer's purchase break; case "payment_intent.payment_failed": intent = (PaymentIntent)stripeEvent.Data.Object; _logger.LogInformation("Failure: {ID}", intent.Id); // Notify the customer that payment failed break; default: // Handle other event types break; } return new EmptyResult(); } catch (StripeException e) { // Invalid Signature return BadRequest(); } } } }

When payment is unsuccessful, you can find more details by inspecting the PaymentIntent’s last_payment_error property. You can notify the customer that their payment didn’t complete and encourage them to try again with a different payment method. Reuse the same PaymentIntent to continue tracking the customer’s purchase.

Handling specific webhook events

The following list describes how to handle webhook events:

Event Description Next steps
processing The customer’s payment was submitted to Stripe successfully. Only applicable to payment methods with delayed success confirmation. Wait for the initiated payment to succeed or fail.
succeeded Customer’s payment succeeded Fulfill the goods or services purchased by the customer
amount_capturable_updated Customer’s payment is authorized and ready for capture Capture the funds that are available for payment
payment_failed Customer’s payment was declined by card network or otherwise expired Reach out to your customer via email or push notification and prompt them to provide another payment method

To test webhooks locally, you can use Stripe CLI. Once you have it installed, you can forward events to your server:

stripe listen --forward-to localhost:4242/webhook Ready! Your webhook signing secret is '{{WEBHOOK_SIGNING_SECRET}}' (^C to quit)

Learn more about setting up webhooks.

Identifying charges on a PaymentIntent

When you attempt to collect payment from a customer, the PaymentIntent creates a Charge. You can inspect the PaymentIntent’s charges.data property to obtain the latest charge:

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' intent = Stripe::PaymentIntent.retrieve('{{PAYMENT_INTENT_ID}}') charges = intent.charges.data
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' intent = stripe.PaymentIntent.retrieve('{{PAYMENT_INTENT_ID}}') charges = intent.charges.data
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $intent = \Stripe\PaymentIntent::retrieve('{{PAYMENT_INTENT_ID}}'); $charges = $intent->charges->data;
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; PaymentIntent intent = PaymentIntent.retrieve("{{PAYMENT_INTENT_ID}}"); List<Charge> charges = intent.getCharges().getData();
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const intent = await stripe.paymentIntents.retrieve('{{PAYMENT_INTENT_ID}}'); const charges = intent.charges.data;
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" intent, err := paymentintent.Get("{{PAYMENT_INTENT_ID}}", nil) charges := intent.Charges.Data
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var service = new PaymentIntentService(); var intent = service.Get("{{PAYMENT_INTENT_ID}}"); var charges = intent.Charges.Data;

A PaymentIntent’s Charge property only contains the most recent charge. To view all of the charges associated with a PaymentIntent, list all charges with the payment_intent​ parameter specified. Note that in addition to the final successful charge, the list includes any unsuccessful charges created during the payment process.

Handling next actions

Some payment methods require additional steps, such as authentication, in order to complete the payment process. The Stripe.js confirmCardPayment function handles these automatically, but some advanced integrations may wish to handle these manually.

The PaymentIntent’s next_action property exposes the next step that your integration must handle in order to complete the payment. The type of possible next actions can differ between various payment methods.

The following is a list of next action types and the payment methods that may require them:

Type Payment methods What it means How to handle
redirect_to_url Cards with 3DS The customer needs to be sent to the provided URL to authenticate the payment. top.location = intent.redirect_to_url.url

You can refer to the documentation for individual payment methods for more details about how to handle their required next actions.

Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.