Learn how to retry failed invoice payments in Stripe using the Dashboard, API, and webhooks. Improve recovery rates with smart retries and customer notifications.
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 Retry Failed Invoice Payments in Stripe
Introduction
Stripe is a popular payment processing platform that allows businesses to handle online payments. Sometimes, invoice payments fail due to various reasons such as insufficient funds, expired cards, or network issues. Fortunately, Stripe provides several ways to retry these failed payments. This tutorial will guide you through the different methods to retry failed invoice payments in Stripe.
Step 1: Understanding Why Payments Fail
Before attempting to retry payments, it's important to understand why they failed in the first place. Common reasons include:
Stripe provides detailed error messages that can help identify the specific reason for payment failure.
Step 2: Accessing Failed Invoices in Stripe Dashboard
To view failed invoices:
Step 3: Manual Retry Through Stripe Dashboard
The simplest way to retry a failed payment is through the Stripe Dashboard:
Step 4: Updating Payment Details Before Retrying
If the payment failed due to invalid card information, you may want to update the customer's payment method before retrying:
Step 5: Retrying Payments Using Stripe API
For automated retries, you can use the Stripe API. Here's how to retry a payment for a specific invoice using the API:
const stripe = require('stripe')('sk_test_your_secret_key');
async function retryInvoicePayment(invoiceId) {
try {
const invoice = await stripe.invoices.pay(invoiceId, {
// Optional: You can specify a different payment method here
// payment_method: 'pm_123456789',
});
console.log('Payment retry successful:', invoice.id);
return invoice;
} catch (error) {
console.error('Error retrying payment:', error.message);
throw error;
}
}
// Example usage
retryInvoicePayment('in\_1234567890');
Step 6: Retrying with a Different Payment Method via API
If you want to retry with a different payment method:
const stripe = require('stripe')('sk_test_your_secret_key');
async function retryWithDifferentPaymentMethod(invoiceId, newPaymentMethodId) {
try {
// Get the customer ID from the invoice
const invoice = await stripe.invoices.retrieve(invoiceId);
const customerId = invoice.customer;
// Update the invoice's default payment method
await stripe.invoices.update(invoiceId, {
default_payment_method: newPaymentMethodId,
});
// Retry the payment
const updatedInvoice = await stripe.invoices.pay(invoiceId);
console.log('Payment retry successful with new method:', updatedInvoice.id);
return updatedInvoice;
} catch (error) {
console.error('Error retrying payment with new method:', error.message);
throw error;
}
}
// Example usage
retryWithDifferentPaymentMethod('in_1234567890', 'pm_new_payment_method');
Step 7: Setting Up Automatic Retries
Stripe offers automatic retries for failed payments. You can configure this in your Stripe Dashboard:
Default retry schedule is typically 3, 5, 7 days after the initial failure.
Step 8: Implementing Smart Retries with Webhooks
You can implement a more sophisticated retry system using Stripe webhooks to listen for payment failure events:
const express = require('express');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();
// Parse JSON body
app.use(express.json());
app.post('/webhook', async (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = 'whsec_your_webhook\_secret';
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle specific events
if (event.type === 'invoice.payment\_failed') {
const invoice = event.data.object;
// Implement custom retry logic
// Example: Check the number of previous attempts
const attemptCount = invoice.attempt\_count;
if (attemptCount < 3) {
// Maybe wait and retry after a calculated delay
// For demonstration, we're retrying immediately
try {
await stripe.invoices.pay(invoice.id);
console.log(`Successfully retried payment for invoice ${invoice.id}`);
} catch (error) {
console.error(`Failed to retry payment: ${error.message}`);
}
} else {
// Consider sending a notification to the customer
console.log(`Maximum retry attempts reached for invoice ${invoice.id}`);
}
}
res.status(200).send('Webhook received');
});
app.listen(3000, () => console.log('Webhook server running on port 3000'));
Step 9: Customizing Retry Logic Based on Failure Reason
Different failure reasons might warrant different retry strategies. Here's how to implement custom retry logic based on the failure code:
async function smartRetryStrategy(invoiceId) {
try {
// Get the invoice and check the last payment error
const invoice = await stripe.invoices.retrieve(invoiceId);
const lastError = invoice.last_payment_error;
if (!lastError) {
console.log('No payment error found');
return;
}
// Create a retry strategy based on the error code
switch (lastError.code) {
case 'card\_declined':
if (lastError.decline_code === 'insufficient_funds') {
// Wait longer for insufficient funds (e.g., 5 days)
setTimeout(() => retryInvoicePayment(invoiceId), 5 _ 24 _ 60 _ 60 _ 1000);
console.log('Scheduled retry in 5 days for insufficient funds');
} else {
// For other card declines, maybe notify the customer to update their card
console.log('Card declined for another reason. Customer notification required.');
}
break;
case 'expired\_card':
// No point in retrying with an expired card - notify customer
console.log('Card expired. Customer notification required.');
break;
case 'processing\_error':
// For processing errors, retry soon as it might be temporary
setTimeout(() => retryInvoicePayment(invoiceId), 30 _ 60 _ 1000); // 30 minutes
console.log('Scheduled retry in 30 minutes for processing error');
break;
default:
// Generic retry strategy for other errors
setTimeout(() => retryInvoicePayment(invoiceId), 24 _ 60 _ 60 \* 1000); // 1 day
console.log('Scheduled generic retry in 1 day');
}
} catch (error) {
console.error('Error implementing smart retry strategy:', error.message);
}
}
// Example usage
smartRetryStrategy('in\_1234567890');
Step 10: Notifying Customers About Failed Payments
It's often helpful to notify customers when their payments fail, giving them the opportunity to update their payment information:
const nodemailer = require('nodemailer');
const stripe = require('stripe')('sk_test_your_secret_key');
async function notifyCustomerAboutFailedPayment(invoiceId) {
try {
// Get invoice details
const invoice = await stripe.invoices.retrieve(invoiceId, {
expand: ['customer'],
});
const customerEmail = invoice.customer.email;
const amount = invoice.amount\_due / 100; // Convert from cents to dollars
const currency = invoice.currency.toUpperCase();
const paymentLink = `https://yourwebsite.com/update-payment?invoice=${invoiceId}`;
// Set up email transporter (replace with your SMTP details)
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587,
secure: false,
auth: {
user: 'your\[email protected]',
pass: 'your\_password',
},
});
// Send email
const info = await transporter.sendMail({
from: '"Your Company" [[email protected]](mailto:[email protected])',
to: customerEmail,
subject: 'Your payment has failed',
html: \`
Payment Failed
Dear customer,
We were unable to process your payment of ${amount} ${currency} for invoice ${invoice.number}.
To update your payment information and complete the payment, please click the link below:
If you have any questions, please contact our support team.
Thank you,
Your Company Team
\`,
});
console.log('Notification email sent:', info.messageId);
return info;
} catch (error) {
console.error('Error sending notification:', error.message);
throw error;
}
}
// Example usage
notifyCustomerAboutFailedPayment('in\_1234567890');
Step 11: Creating a Customer Portal for Self-Service Payment Updates
Stripe offers a Customer Portal that allows customers to update their payment methods themselves:
const stripe = require('stripe')('sk_test_your_secret_key');
async function createCustomerPortalSession(customerId, returnUrl) {
try {
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return\_url: returnUrl,
});
return session.url;
} catch (error) {
console.error('Error creating portal session:', error.message);
throw error;
}
}
// Example route in an Express.js application
app.get('/create-customer-portal', async (req, res) => {
const customerId = req.query.customer\_id;
const returnUrl = 'https://yourwebsite.com/account';
try {
const portalUrl = await createCustomerPortalSession(customerId, returnUrl);
res.redirect(portalUrl);
} catch (error) {
res.status(500).send(`Error: ${error.message}`);
}
});
Step 12: Monitoring Retry Performance
It's important to track how effective your retry strategy is. Here's a simple script to analyze retry performance:
const stripe = require('stripe')('sk_test_your_secret_key');
async function analyzeRetryPerformance(startDate, endDate) {
try {
// Get all invoices that were paid after at least one retry
const invoices = await stripe.invoices.list({
created: {
gte: Math.floor(new Date(startDate).getTime() / 1000),
lte: Math.floor(new Date(endDate).getTime() / 1000),
},
status: 'paid',
limit: 100, // Adjust as needed
});
// Filter and analyze invoices with retry attempts
const retriedInvoices = invoices.data.filter(invoice => invoice.attempt\_count > 1);
const totalRetried = retriedInvoices.length;
const totalAmount = retriedInvoices.reduce((sum, invoice) => sum + invoice.amount\_paid, 0) / 100;
const averageAttempts = retriedInvoices.reduce((sum, invoice) => sum + invoice.attempt\_count, 0) / totalRetried;
return {
period: `${startDate} to ${endDate}`,
totalInvoicesRetried: totalRetried,
totalAmountRecovered: `$${totalAmount.toFixed(2)}`,
averageAttemptsBeforeSuccess: averageAttempts.toFixed(2),
recoveryRate: `${((totalRetried / invoices.data.length) * 100).toFixed(2)}%`
};
} catch (error) {
console.error('Error analyzing retry performance:', error.message);
throw error;
}
}
// Example usage
analyzeRetryPerformance('2023-01-01', '2023-12-31')
.then(report => console.log('Retry Performance Report:', report))
.catch(error => console.error('Analysis failed:', error));
Conclusion
Retrying failed invoice payments in Stripe can be done manually through the Dashboard or programmatically via the API. For best results, consider:
By following these steps, you can significantly improve your payment recovery rate and reduce revenue loss from failed payments.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.