Learn how to accept recurring payments in Stripe with this step-by-step guide covering account setup, API integration, subscription management, webhooks, and testing.
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 Accept Recurring Payments in Stripe: A Comprehensive Guide
Step 1: Set Up Your Stripe Account
Before implementing recurring payments, you need to have a Stripe account.
Step 2: Install the Stripe Library
Depending on your platform, you'll need to install the Stripe library:
For Node.js:
npm install stripe
For PHP:
composer require stripe/stripe-php
For Python:
pip install stripe
For Ruby:
gem install stripe
Step 3: Configure Stripe in Your Application
Initialize the Stripe library with your Secret Key:
For Node.js:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
// Use sk_live_YOUR_SECRET_KEY in production
For PHP:
\Stripe\Stripe::setApiKey('sk_test_YOUR_SECRET_KEY');
// Use sk_live_YOUR_SECRET_KEY in production
For Python:
import stripe
stripe.api_key = 'sk_test_YOUR_SECRET\_KEY'
# Use sk_live_YOUR_SECRET_KEY in production
For Ruby:
require 'stripe'
Stripe.api_key = 'sk_test_YOUR_SECRET\_KEY'
# Use sk_live_YOUR_SECRET_KEY in production
Step 4: Create Products and Prices
For recurring payments, you need to create products and prices in Stripe. This can be done via the Dashboard or API:
Node.js example for creating a product and a recurring price:
// Create a product
const product = await stripe.products.create({
name: 'Premium Subscription',
description: 'Monthly premium subscription',
});
// Create a recurring price for the product
const price = await stripe.prices.create({
product: product.id,
unit\_amount: 1999, // Amount in cents ($19.99)
currency: 'usd',
recurring: {
interval: 'month', // 'day', 'week', 'month', or 'year'
interval\_count: 1, // Bill every 1 month
},
});
Step 5: Set Up Customer Creation
Create a Stripe customer object to track your user's payment information:
// Node.js example
const customer = await stripe.customers.create({
email: '[email protected]',
name: 'Jenny Rosen',
// You can add metadata to store additional information
metadata: {
user\_id: '12345'
}
});
Step 6: Implement Payment Method Collection
You'll need to collect payment method details from your customer. The safest way is using Stripe Elements, a pre-built UI component:
HTML:
<!-- Include Stripe.js -->
<script src="https://js.stripe.com/v3/"></script>
<form id="payment-form">
<div id="card-element">
</div>
<div id="card-errors" role="alert"></div>
<button type="submit">Subscribe</button>
</form>
JavaScript:
// Create a Stripe client
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
// Create an instance of Elements
const elements = stripe.elements();
// Create the card Element and mount it
const cardElement = elements.create('card');
cardElement.mount('#card-element');
// Handle form submission
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
// Create payment method
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing\_details: {
email: '[email protected]', // Use your customer's email
},
});
if (error) {
// Show error to your customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else {
// Send paymentMethod.id to your server
const result = await fetch('/create-subscription', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentMethodId: paymentMethod.id,
customerId: 'cus_your_customer\_id', // This should come from your server
priceId: 'price_your_price\_id', // Use the price ID created earlier
}),
}).then(r => r.json());
if (result.error) {
// Show error to your customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Subscription created successfully
// Redirect or show success message
}
}
});
Step 7: Create the Subscription on Your Server
Handle the subscription creation on your server:
// Node.js example - server-side endpoint
app.post('/create-subscription', async (req, res) => {
const { paymentMethodId, customerId, priceId } = req.body;
try {
// Attach the payment method to the customer
await stripe.paymentMethods.attach(paymentMethodId, {
customer: customerId,
});
// Set it as the default payment method
await stripe.customers.update(customerId, {
invoice\_settings: {
default_payment_method: paymentMethodId,
},
});
// Create the subscription
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
expand: ['latest_invoice.payment_intent'],
});
// Return the subscription details
res.json({
subscriptionId: subscription.id,
clientSecret: subscription.latest_invoice.payment_intent.client\_secret,
status: subscription.status,
});
} catch (error) {
res.status(400).json({ error: { message: error.message } });
}
});
Step 8: Handle Subscription Status
On your client-side, handle the subscription status:
// Handle the client\_secret from the server response
if (result.clientSecret) {
const { error, paymentIntent } = await stripe.confirmCardPayment(result.clientSecret);
if (error) {
// Payment failed
console.error('Payment failed:', error);
} else if (paymentIntent.status === 'succeeded') {
// Payment succeeded, subscription is active
console.log('Subscription active!');
// Update your UI or redirect to a success page
}
}
Step 9: Set Up Webhook Handling
Set up webhooks to receive notifications from Stripe about subscription events:
// Node.js example with Express
const express = require('express');
const app = express();
// Set up webhook endpoint
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = 'whsec_your_webhook_signing_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
switch (event.type) {
case 'invoice.paid':
// Used to provision services after the trial has ended
// The subscription automatically activates
const invoice = event.data.object;
console.log(`Invoice ${invoice.id} paid. Subscription active.`);
break;
case 'invoice.payment\_failed':
// The payment failed or the customer does not have a valid payment method
// The subscription becomes past\_due. Notify the customer
const failedInvoice = event.data.object;
console.log(`Invoice ${failedInvoice.id} payment failed.`);
break;
case 'customer.subscription.updated':
const subscription = event.data.object;
console.log(`Subscription ${subscription.id} updated: ${subscription.status}`);
break;
case 'customer.subscription.deleted':
// Subscription canceled or expired
const canceledSubscription = event.data.object;
console.log(`Subscription ${canceledSubscription.id} canceled.`);
break;
// Add more cases as needed
}
// Return a 200 response to acknowledge receipt of the event
res.json({received: true});
});
Step 10: Test Your Implementation
Test your implementation using Stripe's test cards:
For recurring payments testing, use:
Use the future month/year and any 3-digit CVC.
Step 11: Implement Customer Portal (Optional)
Allow customers to manage their subscriptions through Stripe's Customer Portal:
// Node.js example - server-side endpoint
app.post('/create-customer-portal-session', async (req, res) => {
const { customerId } = req.body;
// Create a customer portal session
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return\_url: 'https://example.com/account',
});
// Return the session URL
res.json({ url: session.url });
});
Front-end code to direct customers to the portal:
// When the customer clicks a "Manage subscription" button
async function redirectToCustomerPortal() {
const response = await fetch('/create-customer-portal-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ customerId: 'cus_your_customer\_id' }),
}).then(r => r.json());
// Redirect to the customer portal
window.location.href = response.url;
}
Step 12: Handle Subscription Cancellations and Updates
If you want to handle cancellations and updates programmatically:
// Cancel a subscription
app.post('/cancel-subscription', async (req, res) => {
const { subscriptionId } = req.body;
try {
const subscription = await stripe.subscriptions.update(subscriptionId, {
cancel_at_period\_end: true,
});
res.json({ subscription });
} catch (error) {
res.status(400).json({ error: { message: error.message } });
}
});
// Update a subscription (e.g., change plan)
app.post('/update-subscription', async (req, res) => {
const { subscriptionId, newPriceId } = req.body;
try {
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
// Update the subscription item with the new price
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [{
id: subscription.items.data[0].id,
price: newPriceId,
}],
});
res.json({ subscription: updatedSubscription });
} catch (error) {
res.status(400).json({ error: { message: error.message } });
}
});
Step 13: Go Live
Once you've thoroughly tested your implementation:
Step 14: Monitor and Manage Subscriptions
Use the Stripe Dashboard to:
Step 15: Implement Dunning Management
Stripe has built-in dunning management to recover failed payments:
Conclusion
You now have a complete system for accepting recurring payments with Stripe! The implementation handles customer creation, payment method collection, subscription management, and all the necessary webhook handling to keep your system in sync with Stripe's subscription events.
Remember to keep your Stripe libraries up to date and stay informed about changes to Stripe's API to ensure your recurring payment system continues to function smoothly.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.