Learn how to verify payment intent status in Stripe API with step-by-step guides, code examples, and best practices for Node.js, Python, PHP, and Ruby.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
How to Verify Payment Intent Status in Stripe API
Step 1: Set up your Stripe account and obtain API keys
Before you can verify payment intent status, you need to have a Stripe account and API keys. Here's how to get them:
Your API keys will look something like this:
Step 2: Install the Stripe library
Depending on your programming language, install the appropriate Stripe library:
For Node.js:
npm install stripe
For Python:
pip install stripe
For PHP:
composer require stripe/stripe-php
For Ruby:
gem install stripe
Step 3: Initialize the Stripe client
Now, initialize the Stripe client with your secret API key:
For Node.js:
const stripe = require('stripe')('sk_test_your_secret_key');
For Python:
import stripe
stripe.api_key = 'sk_test_your_secret\_key'
For PHP:
\Stripe\Stripe::setApiKey('sk_test_your_secret_key');
For Ruby:
require 'stripe'
Stripe.api_key = 'sk_test_your_secret\_key'
Step 4: Retrieve a payment intent by ID
To verify a payment intent status, you first need to retrieve it using its ID:
For Node.js:
async function getPaymentIntent(paymentIntentId) {
try {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
return paymentIntent;
} catch (error) {
console.error('Error retrieving payment intent:', error);
throw error;
}
}
For Python:
def get_payment_intent(payment_intent_id):
try:
payment_intent = stripe.PaymentIntent.retrieve(payment_intent\_id)
return payment\_intent
except Exception as e:
print(f"Error retrieving payment intent: {e}")
raise
For PHP:
function getPaymentIntent($paymentIntentId) {
try {
$paymentIntent = \Stripe\PaymentIntent::retrieve($paymentIntentId);
return $paymentIntent;
} catch (Exception $e) {
echo 'Error retrieving payment intent: ' . $e->getMessage();
throw $e;
}
}
For Ruby:
def get_payment_intent(payment_intent_id)
begin
payment_intent = Stripe::PaymentIntent.retrieve(payment_intent\_id)
return payment\_intent
rescue Stripe::StripeError => e
puts "Error retrieving payment intent: #{e.message}"
raise
end
end
Step 5: Check the payment intent status
After retrieving the payment intent, you can check its status. Here are the possible status values:
For Node.js:
async function checkPaymentStatus(paymentIntentId) {
try {
const paymentIntent = await getPaymentIntent(paymentIntentId);
switch (paymentIntent.status) {
case 'succeeded':
console.log('Payment successful!');
// Handle successful payment
break;
case 'processing':
console.log('Payment is processing.');
// Payment is processing, no action needed
break;
case 'requires_payment_method':
console.log('Payment failed. Please try another payment method.');
// Handle failed payment
break;
case 'requires\_action':
console.log('Additional authentication required.');
// Redirect customer to complete authentication
break;
case 'requires\_capture':
console.log('Payment authorized, needs capture.');
// Proceed with capturing the payment
break;
case 'canceled':
console.log('Payment canceled.');
// Handle canceled payment
break;
default:
console.log(`Unexpected status: ${paymentIntent.status}`);
}
return paymentIntent.status;
} catch (error) {
console.error('Error checking payment status:', error);
throw error;
}
}
For Python:
def check_payment_status(payment_intent_id):
try:
payment_intent = get_payment_intent(payment_intent\_id)
if payment\_intent.status == 'succeeded':
print('Payment successful!')
# Handle successful payment
elif payment\_intent.status == 'processing':
print('Payment is processing.')
# Payment is processing, no action needed
elif payment_intent.status == 'requires_payment\_method':
print('Payment failed. Please try another payment method.')
# Handle failed payment
elif payment_intent.status == 'requires_action':
print('Additional authentication required.')
# Redirect customer to complete authentication
elif payment_intent.status == 'requires_capture':
print('Payment authorized, needs capture.')
# Proceed with capturing the payment
elif payment\_intent.status == 'canceled':
print('Payment canceled.')
# Handle canceled payment
else:
print(f'Unexpected status: {payment\_intent.status}')
return payment\_intent.status
except Exception as e:
print(f"Error checking payment status: {e}")
raise
For PHP:
function checkPaymentStatus($paymentIntentId) {
try {
$paymentIntent = getPaymentIntent($paymentIntentId);
switch ($paymentIntent->status) {
case 'succeeded':
echo 'Payment successful!';
// Handle successful payment
break;
case 'processing':
echo 'Payment is processing.';
// Payment is processing, no action needed
break;
case 'requires_payment_method':
echo 'Payment failed. Please try another payment method.';
// Handle failed payment
break;
case 'requires\_action':
echo 'Additional authentication required.';
// Redirect customer to complete authentication
break;
case 'requires\_capture':
echo 'Payment authorized, needs capture.';
// Proceed with capturing the payment
break;
case 'canceled':
echo 'Payment canceled.';
// Handle canceled payment
break;
default:
echo 'Unexpected status: ' . $paymentIntent->status;
}
return $paymentIntent->status;
} catch (Exception $e) {
echo 'Error checking payment status: ' . $e->getMessage();
throw $e;
}
}
For Ruby:
def check_payment_status(payment_intent_id)
begin
payment_intent = get_payment_intent(payment_intent\_id)
case payment\_intent.status
when 'succeeded'
puts 'Payment successful!'
# Handle successful payment
when 'processing'
puts 'Payment is processing.'
# Payment is processing, no action needed
when 'requires_payment_method'
puts 'Payment failed. Please try another payment method.'
# Handle failed payment
when 'requires\_action'
puts 'Additional authentication required.'
# Redirect customer to complete authentication
when 'requires\_capture'
puts 'Payment authorized, needs capture.'
# Proceed with capturing the payment
when 'canceled'
puts 'Payment canceled.'
# Handle canceled payment
else
puts "Unexpected status: #{payment\_intent.status}"
end
return payment\_intent.status
rescue Stripe::StripeError => e
puts "Error checking payment status: #{e.message}"
raise
end
end
Step 6: Implement a webhook to receive updates
For real-time status updates, set up a webhook to receive event notifications from Stripe.
First, set up the webhook endpoint in your Stripe Dashboard:
Then, implement a webhook handler in your application:
For Node.js (using Express):
const express = require('express');
const app = express();
// Use JSON parser for webhook events
app.use('/stripe-webhook', express.raw({type: 'application/json'}));
app.post('/stripe-webhook', async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
// Create the event from payload and signature using your webhook secret
event = stripe.webhooks.constructEvent(
req.body,
sig,
'whsec_your_webhook\_secret'
);
} catch (err) {
console.log(`Webhook Error: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment\_intent.succeeded':
const paymentIntent = event.data.object;
console.log(`PaymentIntent ${paymentIntent.id} was successful!`);
// Update your database, fulfill the order, etc.
break;
case 'payment_intent.payment_failed':
const failedPaymentIntent = event.data.object;
console.log(`Payment failed for PaymentIntent ${failedPaymentIntent.id}`);
// Notify the customer, update your database, etc.
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
res.status(200).json({received: true});
});
app.listen(3000, () => console.log('Running on port 3000'));
For Python (using Flask):
from flask import Flask, request, jsonify
import stripe
app = Flask(**name**)
stripe.api_key = 'sk_test_your_secret\_key'
webhook_secret = 'whsec_your_webhook_secret'
@app.route('/stripe-webhook', methods=['POST'])
def stripe\_webhook():
payload = request.data
sig\_header = request.headers.get('Stripe-Signature')
try:
event = stripe.Webhook.construct\_event(
payload, sig_header, webhook_secret
)
except ValueError as e:
# Invalid payload
return jsonify({'error': str(e)}), 400
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return jsonify({'error': str(e)}), 400
# Handle the event
if event['type'] == 'payment\_intent.succeeded':
payment\_intent = event\['data']\['object']
print(f"PaymentIntent {payment\_intent['id']} was successful!")
# Update your database, fulfill the order, etc.
elif event['type'] == 'payment_intent.payment_failed':
payment\_intent = event\['data']\['object']
print(f"Payment failed for PaymentIntent {payment\_intent['id']}")
# Notify the customer, update your database, etc.
else:
print(f"Unhandled event type {event['type']}")
return jsonify({'status': 'success'}), 200
if **name** == '**main**':
app.run(port=3000)
For PHP:
type) {
case 'payment\_intent.succeeded':
$paymentIntent = $event->data->object;
echo "PaymentIntent " . $paymentIntent->id . " was successful!";
// Update your database, fulfill the order, etc.
break;
case 'payment_intent.payment_failed':
$paymentIntent = $event->data->object;
echo "Payment failed for PaymentIntent " . $paymentIntent->id;
// Notify the customer, update your database, etc.
break;
default:
echo "Unhandled event type " . $event->type;
}
http_response_code(200);
?>
For Ruby (using Sinatra):
require 'sinatra'
require 'stripe'
require 'json'
Stripe.api_key = 'sk_test_your_secret\_key'
webhook_secret = 'whsec_your_webhook_secret'
post '/stripe-webhook' do
payload = request.body.read
sig_header = request.env['HTTP_STRIPE\_SIGNATURE']
begin
event = Stripe::Webhook.construct\_event(
payload, sig_header, webhook_secret
)
rescue JSON::ParserError => e
# Invalid payload
halt 400, { error: e.message }.to\_json
rescue Stripe::SignatureVerificationError => e
# Invalid signature
halt 400, { error: e.message }.to\_json
end
# Handle the event
case event.type
when 'payment\_intent.succeeded'
payment\_intent = event.data.object
puts "PaymentIntent #{payment\_intent.id} was successful!"
# Update your database, fulfill the order, etc.
when 'payment_intent.payment_failed'
payment\_intent = event.data.object
puts "Payment failed for PaymentIntent #{payment\_intent.id}"
# Notify the customer, update your database, etc.
else
puts "Unhandled event type #{event.type}"
end
status 200
{ received: true }.to\_json
end
Step 7: Capturing a payment (for uncaptured payments)
If the payment intent status is "requires_capture" (which happens with manual capture), you'll need to capture the payment:
For Node.js:
async function capturePayment(paymentIntentId) {
try {
const paymentIntent = await stripe.paymentIntents.capture(paymentIntentId);
console.log('Payment captured successfully!');
return paymentIntent;
} catch (error) {
console.error('Error capturing payment:', error);
throw error;
}
}
For Python:
def capture_payment(payment_intent\_id):
try:
payment_intent = stripe.PaymentIntent.capture(payment_intent\_id)
print('Payment captured successfully!')
return payment\_intent
except Exception as e:
print(f"Error capturing payment: {e}")
raise
For PHP:
function capturePayment($paymentIntentId) {
try {
$paymentIntent = \Stripe\PaymentIntent::capture($paymentIntentId);
echo 'Payment captured successfully!';
return $paymentIntent;
} catch (Exception $e) {
echo 'Error capturing payment: ' . $e->getMessage();
throw $e;
}
}
For Ruby:
def capture_payment(payment_intent\_id)
begin
payment_intent = Stripe::PaymentIntent.capture(payment_intent\_id)
puts 'Payment captured successfully!'
return payment\_intent
rescue Stripe::StripeError => e
puts "Error capturing payment: #{e.message}"
raise
end
end
Step 8: Implementing a complete verification workflow
Here's a complete example of how to implement payment verification in a real application:
For Node.js:
async function verifyAndProcessPayment(paymentIntentId) {
try {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
// Log the current status
console.log(`Payment status: ${paymentIntent.status}`);
// Process based on status
switch (paymentIntent.status) {
case 'succeeded':
// Payment was successful, fulfill the order
await fulfillOrder(paymentIntent.metadata.orderId);
return { success: true, message: 'Payment successful. Order fulfilled.' };
case 'requires\_capture':
// Payment is authorized but needs capture
const capturedPayment = await stripe.paymentIntents.capture(paymentIntentId);
await fulfillOrder(capturedPayment.metadata.orderId);
return { success: true, message: 'Payment captured. Order fulfilled.' };
case 'processing':
// Payment is still processing
await updateOrderStatus(paymentIntent.metadata.orderId, 'processing');
return { success: true, message: 'Payment is processing. Order status updated.' };
case 'requires_payment_method':
// The initial payment attempt failed
await updateOrderStatus(paymentIntent.metadata.orderId, 'payment\_failed');
return { success: false, message: 'Payment failed. Customer needs to try again.' };
case 'requires\_action':
// Customer needs to complete additional authentication
return {
success: false,
message: 'Additional authentication required.',
requires\_action: true,
payment_intent_client_secret: paymentIntent.client_secret
};
case 'canceled':
// Payment was canceled
await updateOrderStatus(paymentIntent.metadata.orderId, 'payment\_canceled');
return { success: false, message: 'Payment was canceled.' };
default:
// Unknown status
await updateOrderStatus(paymentIntent.metadata.orderId, 'unknown_payment_status');
return { success: false, message: `Unexpected payment status: ${paymentIntent.status}` };
}
} catch (error) {
console.error('Error processing payment:', error);
// Log error and update order status
await updateOrderStatus(error.metadata?.orderId, 'payment\_error');
return { success: false, message: 'An error occurred while processing the payment.' };
}
}
// Example helper functions
async function fulfillOrder(orderId) {
// Logic to fulfill the order (e.g., ship products, send email, etc.)
console.log(`Fulfilling order ${orderId}`);
}
async function updateOrderStatus(orderId, status) {
// Logic to update the order status in your database
console.log(`Updating order ${orderId} to status: ${status}`);
}
// Example usage in an Express route
app.get('/verify-payment/:paymentIntentId', async (req, res) => {
try {
const result = await verifyAndProcessPayment(req.params.paymentIntentId);
res.json(result);
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
});
Step 9: Handling errors and edge cases
When verifying payment intents, be sure to handle these common errors and edge cases:
Here's an example of handling these cases:
async function verifyPaymentWithErrorHandling(paymentIntentId) {
try {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
return { success: true, status: paymentIntent.status, paymentIntent };
} catch (error) {
// Handle specific Stripe errors
if (error.type === 'StripeInvalidRequestError') {
if (error.code === 'resource\_missing') {
return { success: false, error: 'Payment not found. It may have been deleted or the ID is incorrect.' };
}
}
if (error.type === 'StripePermissionError') {
return { success: false, error: 'No permission to access this payment.' };
}
if (error.type === 'StripeRateLimitError') {
// Implement exponential backoff and retry
console.log('Rate limited by Stripe. Retrying...');
await new Promise(resolve => setTimeout(resolve, 1000));
return verifyPaymentWithErrorHandling(paymentIntentId);
}
if (error.type === 'StripeAPIError' || error.type === 'StripeConnectionError') {
// Network or API error - retry with backoff
console.log('Stripe API error. Retrying...');
await new Promise(resolve => setTimeout(resolve, 2000));
return verifyPaymentWithErrorHandling(paymentIntentId);
}
// General error
console.error('Error verifying payment:', error);
return { success: false, error: 'An unexpected error occurred while verifying the payment.' };
}
}
Step 10: Implementing idempotency for reliability
When making API calls to Stripe, especially when capturing payments, use idempotency keys to prevent duplicate operations:
async function capturePaymentWithIdempotency(paymentIntentId, idempotencyKey) {
try {
const paymentIntent = await stripe.paymentIntents.capture(
paymentIntentId,
{ idempotency\_key: idempotencyKey }
);
console.log('Payment captured successfully!');
return paymentIntent;
} catch (error) {
console.error('Error capturing payment:', error);
throw error;
}
}
// Example usage:
const idempotencyKey = `capture_${paymentIntentId}_${Date.now()}`;
await capturePaymentWithIdempotency(paymentIntentId, idempotencyKey);
Conclusion
By following these steps, you should now have a robust system for verifying payment intent status in the Stripe API. This approach ensures that you can track payments throughout their lifecycle, handle various status changes appropriately, and reliably process both successful and failed payments.
Remember to always use test API keys during development and to thoroughly test your integration before going live. Stripe provides a test mode that allows you to simulate various payment scenarios without processing real transactions.
For more information, refer to the official Stripe documentation at https://stripe.com/docs/payments/payment-intents.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.