Startup Kit

Django Stripe Subscriptions: How To Guide

By Andrew on 9/27/2024

Django Stripe Subscriptions: How To Guide

This guide will walk you through implementing Stripe subscriptions using a Django backend (with dj-stripe) and a React frontend. We’ll cover setting up the backend API, creating React components for subscription management, and handling the communication between the two.

If you just want an out of the box, production-ready solution to do stripe subscriptions, authentication, and much more, check out our starter kit: slimsaas kit.

Table of Contents

  1. Prerequisites
  2. Backend Setup
  3. Frontend Setup
  4. API Endpoints
  5. React Components
  6. Stripe Elements Integration
  7. Handling Subscriptions
  8. Webhook Handling
  9. Testing
  10. Best Practices

Prerequisites

Backend Setup

  1. Install necessary packages:

    pip install djangorestframework django-cors-headers
    
  2. Update settings.py:

    INSTALLED_APPS = [
        # ...
        'rest_framework',
        'corsheaders',
    ]
    
    MIDDLEWARE = [
        'corsheaders.middleware.CorsMiddleware',
        # ... other middleware
    ]
    
    CORS_ALLOWED_ORIGINS = [
        "http://localhost:3000",  # Your React app's address
    ]
    
  3. Create serializers for your subscription data:

    # serializers.py
    from rest_framework import serializers
    from djstripe.models import Product, Price, Subscription
    
    class ProductSerializer(serializers.ModelSerializer):
        class Meta:
            model = Product
            fields = ['id', 'name', 'description']
    
    class PriceSerializer(serializers.ModelSerializer):
        class Meta:
            model = Price
            fields = ['id', 'unit_amount', 'currency', 'recurring']
    
    class SubscriptionSerializer(serializers.ModelSerializer):
        class Meta:
            model = Subscription
            fields = ['id', 'status', 'current_period_end']
    

Frontend Setup

  1. Install necessary packages:

    npm install @stripe/stripe-js @stripe/react-stripe-js axios
    
  2. Set up Stripe in your React app:

    // src/stripe.js
    import { loadStripe } from '@stripe/stripe-js';
    
    const stripePromise = loadStripe('your_publishable_key');
    
    export default stripePromise;
    

API Endpoints

Create views and URLs for your subscription API:

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from djstripe.models import Product, Price, Subscription
from .serializers import ProductSerializer, PriceSerializer, SubscriptionSerializer

class ProductListView(APIView):
    def get(self, request):
        products = Product.objects.filter(active=True)
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

class CreateSubscriptionView(APIView):
    def post(self, request):
        # Implementation for creating a subscription
        pass

# urls.py
from django.urls import path
from .views import ProductListView, CreateSubscriptionView

urlpatterns = [
    path('products/', ProductListView.as_view()),
    path('create-subscription/', CreateSubscriptionView.as_view()),
]

React Components

Create components for displaying and managing subscriptions:

// src/components/ProductList.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const ProductList = () => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const fetchProducts = async () => {
      const response = await axios.get('http://localhost:8000/api/products/');
      setProducts(response.data);
    };
    fetchProducts();
  }, []);

  return (
    <div>
      <h2>Available Subscriptions</h2>
      {products.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>{product.description}</p>
          {/* Add subscription button here */}
        </div>
      ))}
    </div>
  );
};

export default ProductList;

Stripe Elements Integration

Integrate Stripe Elements for secure payment collection:

// src/components/CheckoutForm.js
import React from 'react';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';

const CheckoutForm = ({ priceId }) => {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (event) => {
    event.preventDefault();
    
    if (!stripe || !elements) {
      return;
    }

    const result = await stripe.createPaymentMethod({
      type: 'card',
      card: elements.getElement(CardElement),
    });

    if (result.error) {
      console.log(result.error.message);
    } else {
      // Send paymentMethod.id to your server
      const response = await axios.post('http://localhost:8000/api/create-subscription/', {
        payment_method_id: result.paymentMethod.id,
        price_id: priceId,
      });
      // Handle the response
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      <button type="submit" disabled={!stripe}>Subscribe</button>
    </form>
  );
};

export default CheckoutForm;

Handling Subscriptions

On the backend, handle the subscription creation:

# views.py
import stripe
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from djstripe.models import Customer

stripe.api_key = settings.STRIPE_SECRET_KEY

class CreateSubscriptionView(APIView):
    def post(self, request):
        try:
            # Get the Stripe Customer
            customer = Customer.objects.get(subscriber=request.user)
            
            # Create the subscription
            subscription = stripe.Subscription.create(
                customer=customer.id,
                items=[{'price': request.data['price_id']}],
                payment_behavior='default_incomplete',
                expand=['latest_invoice.payment_intent'],
            )

            return Response({
                'subscriptionId': subscription.id,
                'clientSecret': subscription.latest_invoice.payment_intent.client_secret
            })
        except Exception as e:
            return Response({'error': str(e)}, status=400)

Webhook Handling

Ensure your webhook handling (as set up with dj-stripe) is working correctly to process subscription events.

Testing

  1. Test the entire flow from selecting a product to completing a subscription
  2. Use Stripe’s test cards to simulate various scenarios
  3. Verify that subscription status is correctly updated in both Stripe and your database

Best Practices

  1. Always use HTTPS in production
  2. Implement proper error handling and user feedback
  3. Use Stripe’s test mode during development
  4. Keep your Stripe API keys and webhook secrets secure
  5. Regularly update your dependencies (React, Stripe libraries, dj-stripe)
  6. Implement logging for easier debugging
  7. Consider implementing a user dashboard for subscription management

This guide provides a foundation for integrating React with Django and Stripe for subscription handling. Remember to consult the official documentation for React, Django, dj-stripe, and Stripe for the most up-to-date information and best practices.

If your interested in a more comprehensive, production ready solution that solves for all the headaches setting up stripe, authentication, and much more, check out the slimsaas kit

Get SlimSaaS!