/stripe-guides

How to accept recurring payments in Stripe?

Learn how to accept recurring payments in Stripe with this step-by-step guide covering account setup, API integration, subscription management, webhooks, and testing.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to accept recurring payments in Stripe?

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.

  • Visit Stripe.com and sign up for an account
  • Complete the verification process
  • Navigate to the Dashboard to find your API keys
  • Note down both your Publishable Key and Secret Key (we'll need these later)

 

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:

  • Success: 4242 4242 4242 4242
  • Requires Authentication: 4000 0025 0000 3155
  • Declined: 4000 0000 0000 0002

For recurring payments testing, use:

  • Always succeeds: 4242 4242 4242 4242
  • First payment requires authentication, future payments succeed: 4000 0027 6000 3184
  • First payment succeeds, future payments fail: 4000 0000 0000 3220

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:

  • Switch from test keys to live keys
  • Update your webhook endpoints with production URLs
  • Set up live webhook signing secrets
  • Configure your customer email templates in the Stripe Dashboard
  • Ensure your privacy policy and terms of service are updated to reflect subscription billing

 

Step 14: Monitor and Manage Subscriptions

 

Use the Stripe Dashboard to:

  • Monitor active subscriptions
  • Track revenue and churn
  • Handle customer support issues
  • Process refunds when necessary
  • Set up email notifications for important events

 

Step 15: Implement Dunning Management

 

Stripe has built-in dunning management to recover failed payments:

  • Navigate to Settings > Billing > Subscription Settings in your Stripe Dashboard
  • Configure Smart Retries to automatically retry failed payments
  • Set up dunning emails to notify customers about payment failures
  • Customize the number of retry attempts and intervals

 

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.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022