Creating a Subscription SaaS Application with Django and Stripe
Tags: django, stripe, webdev, python
Published: 2020-05-28 09:21:23

---
title: Creating a Subscription SaaS Application with Django and Stripe
published: true
description: All the technical details of creating a subscription SaaS business using the Python-based Django web framework and Stripe payment processor
tags: django, stripe, webdev, python
canonical_url: https://www.saaspegasus.com/guides/django-stripe-integrate/
cover_image:
---


![Django + Stripe = Win](https://www.saaspegasus.com/static/images/web/django-stripe-integration/django-stripe-headline.png)

Software-as-a-service (SaaS) subscription businesses are among the fastest-growing companies in the world today. Every day, developers and aspiring entrepreneurs break code on a new subscription SaaS product. But what do these apps look like under the hood?

This guide will cover all the technical details of creating a subscription SaaS business using the Python-based [Django web framework](https://www.djangoproject.com/) and [Stripe payment processor](https://stripe.com/).  

<p class="notification is-info is-light">
Here's <a href="https://www.saaspegasus.com/subscriptions/">an interactive demo of what we'll be building</a>.
</p>

By the time you've finished the article you should have solid foundational knowledge of everything needed to build a subscription SaaS application&mdash;and if you follow along&mdash;a fully-functional implementation of subscriptions in your own Django project.

## Who should read this

**This guide is written primarily for *developers who want to add paid subscriptions to their application*.**

It is specifically focused on the [Django web framework](https://www.djangoproject.com/) and  [Stripe](https://stripe.com/) subscriptions.

If you're a developer using a different technology stack you'll still benefit from the high-level modeling and architectural sections, but may struggle to follow along with some of the code examples, as we assume basic Python and Django knowledge. We also focus heavily on a Stripe integration&mdash;though much of the guide holds for Paypal, Braintree or other payment gateways.

## What you'll need

In order to follow along you'll need:

- **A basic [Django project](https://www.djangoproject.com/start/).** The examples use Django 3.0 and Python 3.8.
- **A free [Stripe account](https://dashboard.stripe.com/register).** Test mode is fine. 


<section class="notification is-warning is-light" >
  Note: We are <em>not</em> going to start from scratch but rather assume you    have a functional Django project that you want to add Stripe subscriptions to.   To get started with Django it's recommended to follow the <a href="https://www.djangoproject.com/start/">official tutorial</a>.
</section>

## An overview of subscriptions

Before getting into the details of the integration, we'll first take a moment to cover what subscriptions are and why you would want to use them.

If you're already familiar with subscriptions and convinced you want them, feel free to skim this section and/or skip ahead!  

### What are subscriptions?

Most businesses that sell software operate in one of two ways:

1. **Charge a single amount for a product or access to a service.** This is also known as a *one-time sale* model.    Historically, this was the most common way to sell software, and the model is still common in a lot of    desktop software, mobile apps, and games.
1. **Charge a recurring amount on a recurring basis, typically each month or year.**     This is a *subscription* model&mdash;often also referred to as as a software as a service (SaaS) businesses.     Spotify, Netflix, Salesforce, and Zoom are all subscription businesses.

There are other software business models&mdash;including advertising-based (e.g. Google, Facebook),  or marketplace/transaction-fee based (e.g. AirBNB, Stripe)&mdash;but in this post we're focusing on the *subscription model*.
 
### Why would you want to use subscriptions?

As loads of startup advice will tell you, **subscription revenue is the holy grail of business models**. Because, instead of collecting a one-time sale from your customers, or relying on consistent traffic for advertising, you collect *predictable, recurring revenue* from your customers.

> **"Recurring revenue is *the only way*."**
> &mdash;Jason Cohen, Founder, WP Engine 
> [Designing the Ideal Bootstrapped Business](https://www.youtube.com/watch?v=otbnC2zE2rw)

Having subscription revenue makes it easier to model and forecast your business, since you can quantitatively learn how many new customers you acquire in a month and how many will will cancel (a.k.a. churn). This allows you to very reliably understand how much money you can expect to earn next month, next quarter, and next year.

Subscriptions are also generally a way to increase a customer's *life time value* (LTV)&mdash;the amount of money the they pay you to use your product over time. By charging a smaller amount on a recurring basis you will typically, over the lifetime of a customer, be  able to collect substantially more total revenue than from a single-time purchase.

Even software products that have historically had a very successful one-time-sale model like Adobe Photoshop have now switched to a subscription model.

![Adobe Pricing](https://www.saaspegasus.com/static/images/web/django-stripe-integration/adobe-pricing.png)
<figcaption>
Remember when Adobe Photoshop "only" cost $1000?
</figcaption>

### How should you structure your subscriptions?

Ok, so you're convinced you want to offer subscriptions to your product. The next question you'll face, is how to set them up. There are a number of choices you'll have to make, including:

* **How many different pricing tiers will the product have and what will the prices be?**
* **Will there be a free tier? What about a trial?**
* **What will be included in each tier?** Common options include limiting access to certain features,   as well as setting limits on usage&mdash;e.g. only allowing a certain number of users or events on a particular tier.
* **What billing options will you offer?** Most apps offer at least a monthly plan and a discounted annual plan.
* **Will you offer a single pricing structure, or charge based on usage?** For example, most email sending platforms charge based on the number of mails you send.

**Unfortunately there is no one-size-fits-all answer to these questions.**  Most of the answers will be specific to the application that you are developing, and as the business owner you are hopefully more qualified than anyone else (certainly this article) to make those choices.


For the purposes of this post, we'll go with one of the most common options: *a freemium application with multiple pricing tiers and monthly and annual billing options.*

## Subscription data modeling

Subscription billing systems are *complicated*.

They involve many moving parts, both on the setup side (what subscriptions you offer) and on the payment side  (the details required to collect a payment from a customer and sign them up to a plan). 

On the setup side you need to model what tiers exist, how they map to different features in your application,  and how much they cost for various time intervals (e.g. monthly, annual, etc.).

And on the payment side you need to model the individual payment details as well as information about  the customer and subscription, including the plan they are on, payment status, and renewal details.

It's a lot of stuff!

Thankfully Stripe has thought this problem through for us and created [Stripe billing](https://stripe.com/billing)
to model everything we'll need. **Therefore, we'll largely be relying on Stripe's billing models and just annotating and referencing them a bit in our Django application.** This drastically simplifies the amount of modeling we have to do on our own.

It also means that by and large **Stripe will be the primary source of truth for most information**,  and our Django application will (mostly) just grab a read-only copy of whatever it needs from Stripe.

### The Stripe Billing models we'll be using

Stripe's payment and billing system is large and complex, but for the most part we'll be able to focus on four key models&mdash;two on the setup side and two on the payment side.

![Stripe Models](https://www.saaspegasus.com/static/images/web/django-stripe-integration/stripe-models.png)

<figcaption>
By using Stripe's existing data models we can keep most of the complexity outside our application. These are the main models we'll be using, from <a href="https://stripe.com/billing">Stripe Billing</a>.
</figcaption>

**In setup we'll primarily be using [*Products*](https://stripe.com/docs/api/service_products) and [*Plans*](https://stripe.com/docs/api/plans).**

From [Stripe's documentation](https://stripe.com/docs/billing/subscriptions/modeling):

>*"Two core models make up subscriptions, the heart of Stripe's recurring billing system: [Product](https://stripe.com/docs/billing/subscriptions/products-and-plans#products) and [Plan](https://stripe.com/docs/billing/subscriptions/products-and-plans#plans-detail). A Product defines the product or service you offer, while a Plan represents how to charge for that Product. The product can be anything you charge for on a recurring basis, such as a digital SaaS product, a base fee for phone service, or a service that comes to your home and washes your car every week."*

In the SaaS world (and in the rest of this example) *Products* are the primary model that map to your pricing tiers / feature  sets&mdash;e.g. "Pro" and "Enterprise", and then *Plans* are the options users have for signing up for those products, e.g. "Monthly", "Annual", or "Student Rate".


<section class="notification is-warning is-light" >
  SaaS applications often refer to the things that Stripe calls "Products" as "Plans".   To help mitigate this confusion, this guide uses the word <em>tiers</em> when referring to the Products/Plans   that you offer your end-users.
</section>



**For payment-processing we'll focus on [*Subscriptions*](https://stripe.com/docs/api/subscriptions) and [*Customers*](https://stripe.com/docs/api/customers/object).**

The main model we'll be referencing is the Subscription&mdash;which will allow you to charge a Customer on a recurring basis. However, creating Subscriptions and collecting payments will require also working with Customers and so we'll cover those too.

Some other models will be needed to collect card and payment details, but they're not critical to the  architecture of our system and we'll discuss them when we encounter them. 

### Modeling and syncing data between your application and Stripe

So, at a high level we're going to keep our Products, Plans, Subscriptions and Customers in Stripe. But we still need to be able to reference them in our own application so we can do things like:

- Show a pricing page with our different pricing tiers on it
- Determine whether a user has an active subscription so they can access a particular feature
- Send invoices or payment reminders to our customers

So how will we them up?

As we mentioned above, we'll mostly thinking of Stripe as the "master" copy of our data and treat our application as a read-only replica.

What does that look like, practically?

There are two possible approaches.

**Approach 1: Store the Stripe *IDs* of the various objects we'll be using.**

In this approach, all we ever store is the Stripe *ID* of the object in question. Then, whenever we need more information about that object&mdash;say to find out the price of a particular Plan&mdash;we'd query the [Stripe API](https://stripe.com/docs/api) with the appropriate ID and get back the information we need.

```python
class MyStripeModel(models.Model):
    name = models.CharField(max_length=100)
    stripe_subscription_id = models.CharField(max_length=100)
```
<figcaption>Approach 1: Referencing just the Stripe IDs.</figcaption>

This keeps things quite simple on our side&mdash;we don't need to maintain any local state or worry about keeping data in sync apart from the IDs. Any time we need data, we get it from Stripe and we are guaranteed that the information is up-to-date.

The main problem with this approach is performance.  Remote requests are *expensive*&mdash;and so if you're trying to keep your page load times down, minimizing the number of external API calls your application makes can be important. Performance issues can be mitigated with caching, but this isn't always an easy option.

**Approach 2: Keep a copy of the Stripe models in your local application database.**

In this approach we use code that keeps our Stripe data in sync with our local application database. Then, rather than going back to Stripe every time we need information about a particular piece of data, we can just look it up in our application DB. This solves the performance issue above, and makes it much easier to work with Stripe data&mdash;we can just use the ORM we use for the rest of our data.

```python
class StripeSubscription(models.Model):
    start_date = models.DateTimeField(help_text="The start date of the subscription.")
    status = models.CharField(max_length=20, help_text="The status of this subscription.")
    # other data we need about the Subscription from Stripe goes here 


class MyStripeModel(models.Model):
    name = models.CharField(max_length=100)
    stripe_subscription = models.ForeignKey(StripeSubscription)
```

<figcaption>Approach 2: Keeping a local copy of the Stripe data we need.</figcaption>

The problem with this approach is that, *data synchronization is hard*.

If we're not careful, our local copy of the data can get out of sync with Stripe, and then bad things can happen to our users. Imagine if someone signed up for a $10/month plan and then got billed $20 for the first month because our data was out of sync! They'd probably be pretty unhappy.

**So, which approach is better?**

For simple setups it's probably better to go with Approach 1 and only store Stripe IDs. This can get you pretty far and you can always change plans if performance becomes a problem or you encounter workflows that require having more data in your application.

***However, specifically for Django applications, we recommend Approach 2***. This is primarily because of the great [dj-stripe library](https://github.com/dj-stripe/dj-stripe) that handles keeping our data in sync with Stripe with very little effort, allows us to reap the performance benefit of having the data locally, and lets us interface with our Stripe data through the ORM.

If `dj-stripe` didn't exist, we'd recommend Approach 1 for getting off the ground,  but since it does, *we'll go with Approach 2 and use it throughout the rest of this guide*.
 
## Setting up your Stripe billing models

Ok with our big-picture modeling out of the way we can finally start getting our hands dirty.

The first thing we're going to do is set up our Products and Plans in Stripe. You may want to follow [Stripe's guide to setting up a Subscription](https://stripe.com/docs/billing/subscriptions/set-up-subscription)  as we get started. We'll reference this page heavily throughout this article. 

<section class="notification is-warning is-light" >
  It is <strong>strongly</strong> recommended that you work in your Stripe test account while you are doing development.
</section>

This guide will use a relatively generic set of Subscription options: three Products named "Starter", "Standard", and "Premium", with two Plans each ("Monthly" and "Annual").

So, starting with *[Step 1](https://stripe.com/docs/billing/subscriptions/cards#create-product-plan-code)*  in the guide, go ahead and create three Products with the names above (or use your own if you prefer). For each Product, add two pricing plans&mdash;one billed monthly and one billed annually. Set your prices up however you like. In our example, we've made the Annual plan cost 10x the monthly (so you get two months free by opting for annual billing&mdash;a common SaaS pricing model).

When you're done your Product list should look something like this:

![Product List](https://www.saaspegasus.com/static/images/web/django-stripe-integration/stripe-products.png)
<figcaption>
Your list of Products in Stripe
</figcaption>

And in each Product the list of Plans should look something like this:

![Standard Plans](https://www.saaspegasus.com/static/images/web/django-stripe-integration/stripe-standard-plans.png)
<figcaption>
The Pricing Plans section for each individual Product
</figcaption>

Done? Great!

Let's get coding! 

## Syncing your Stripe billing data to your Django application

Now that your data is in Stripe it's time to sync it to your Django application.

Remember that library [dj-stripe](https://github.com/dj-stripe/dj-stripe) that we mentioned above? This is where it starts to come in handy.

### Setting up and configuring dj-stripe

First we'll need to setup `dj-stripe`. Follow the instructions on [their installation documentation](https://dj-stripe.readthedocs.io/en/stable/installation.html), by running `pip install dj-stripe` (and/or adding it to your `requirements.txt` file) and adding the `"djstripe"` app to your `INSTALLED_APPS` in `settings.py` like below.

```python
INSTALLED_APPS =(
    # other apps here
    "djstripe",
)
```

You will also need to set the API keys in your `settings.py`. As already mentioned, we'll be using test mode, so make sure at least the variables below are set. You can your keys from [this page](https://dashboard.stripe.com/test/apikeys). 

```python
STRIPE_TEST_PUBLIC_KEY = os.environ.get("STRIPE_TEST_PUBLIC_KEY", "<your publishable key>")
STRIPE_TEST_SECRET_KEY = os.environ.get("STRIPE_TEST_SECRET_KEY", "<your secret key>")
STRIPE_LIVE_MODE = False
DJSTRIPE_WEBHOOK_SECRET = "whsec_xxx"  # We don't use this, but it must be set
```

This example allows you to use os environment variables so you don't have to store your secrets in a .py file. However, if you're not familiar with environment variables and are setting things up locally with your test account it's fine to add the keys directly where it says `"<your key>"`.

<section class="notification is-warning is-light" >
 Note: Make sure never to check any keys into source control!
</section>


Once you've added your keys, you will need to create the `dj-stripe` database tables:

```bash
./manage.py migrate
```

If this command fails it's likely that something isn't set up properly (the command should provide more details). If that happens, double check your setup and make sure it's working before continuing on.


### Bootstrapping your initial Products and Plans in Django

With `dj-stripe` set up, syncing our Products and Plans is now trivial.

Just run the following built-in command:

```bash
python manage.py djstripe_sync_plans_from_stripe
```

If everything is setup properly you should see output that looks like this:

```
Synchronized plan plan_GzAdqfExNKGmPz
Synchronized plan plan_GzAbnphUgi7vLI
Synchronized plan plan_GqvX7B8467f2Cj
Synchronized plan plan_GqvXkzAvxlF0wR
Synchronized plan plan_GqvV8KsEKyjzSN
Synchronized plan plan_GqvV4aKw0sh0Za
```

You should see one Plan ID per pricing plan you set up (6 total if you used the suggested setup above).

**What just happened?**

Behind the scenes `dj-stripe` looked into your Stripe account, found all your Products and Plans and synced them to your local database. If you go to your local Django Admin UI (by default at [http://localhost:8000/admin/djstripe/product/](http://localhost:8000/admin/djstripe/product/)) you should now see the Stripe Products you set up earlier.

![Products in Django Admin](https://www.saaspegasus.com/static/images/web/django-stripe-integration/django-admin-products.png)
<figcaption>
Your Plans should look something like this in the Django admin interface
</figcaption>


**Why was this useful?**

Well, now that we have the data in our database we can start using it in our Django application! Let's do that next.

## Working with Products and Plans

To get started we're going to run through a few examples using just Products and Plans. Once that's out of the way we'll move on to Subscriptions.

### Creating a Pricing Page

The first thing we might want to do is create a pricing page. This is where our potential customers can see our different tiers, how much they cost, and what's in them. It's also the place where&mdash;eventually&mdash;they'll be able to subscribe to a plan.

Let's start by setting up the UI.

Since all our data is now synchronized from Stripe, we won't have to go back to Stripe to get the data but can just inspect our local `dj-stripe` models.

At a very basic level, that might look something like the below.

<section class="notification is-info is-light" >
  Note: If the files/terminology below don't make sense, you may need to 
  <a href="https://docs.djangoproject.com/en/3.0/intro/tutorial03/">brush up on Django views</a>.
</section>

**1. Set up a URL route**

In `urls.py`:

```python
urlpatterns = [
    path(pricing_page/', views.pricing_page, name='pricing_page'),
]
```

**2. Create the View**

In `views.py`:

```python
from django.shortcuts import render
from djstripe.models import Product

def pricing_page(request):
    return render(request, 'pricing_page.html', {
        'products': Product.objects.all()
    })
```

Notice how we are just grabbing the `Products` from our database using the ORM instead of hitting the Stripe API.  We can do this because we have synced our Stripe data to our application DB.

**3. Create the Template**

In `pricing_page.html`:

```html
{% raw %}
<section>
  <p class="title">Pricing Plans</p>
  <div class="columns">
    {% for product in products %}
      <div class="column">
        <p class="subtitle">{{ product.name }}</p>
        {% for plan in product.plan_set.all %}
          <div>
            <p class="heading">{{ plan.nickname }}</p>
            <p>{{ plan.human_readable_price }}</p>
          </div>
        {% endfor %}
      </div>
    {% endfor %}
  </div>
</section>
{% endraw %}
```

If you've set things up properly this should render a page that looks something like the below (you'll need to have [Bulma CSS](https://bulma.io/) on the page for the styling to work).

![Basic Pricing Plan Page](https://www.saaspegasus.com/static/images/web/django-stripe-integration/pricing-page-simple.png)

<figcaption>
Our bare-bones pricing page, using the Product and Plan data from Stripe
</figcaption>

Not bad for a few lines of code!

But&mdash;we probably want to display a bunch more stuff than just the names and prices.

A real SaaS pricing page might look more like this.

![Fancier Pricing Plan Page](https://www.saaspegasus.com/static/images/web/django-stripe-integration/pricing-page-2.png)

<figcaption>
A more fleshed-out pricing page, from <a href="https://www.saaspegasus.com/subscriptions/" target="_blank">our live demo</a>.
</figcaption>

Here we've added three pieces of information to each tier:

1. A description/tagline saying more about the tier and who it's for. 
1. A list of features that are available.
1. Whether the tier is the default&mdash;which is highlighted in the UI.

To make this page we're going to need to create some additional *metadata* around the Products and Plans.

### Adding metadata to your Stripe objects

Ok, so we want to attach the above information to our pricing tiers.

How should we do that?

Once again, we are faced with several options:

1. **We could store this information in Stripe.** Stripe allows arbitrary "metadata" to be attached to objects, so we could store it there, sync it to our database, and then display it, similar to the other pieces of information.
1. **We could store the information in our application database.**     Since all of this data is application-specific there's not really a need for it to be in Stripe.    So we could just create a local Database model to store it.     Then we don't have to use Stripe's clunky metadata UI or worry about sync issues.    This seems more appealing.
1. **We could store the information in code.** If this data is coupled with our application anyways,    we could bypass the database entirely and just keep it in our source code.    This simplifies things even more&mdash;though comes with the downside of requiring code changes to make changes.
   
**This guide recommends keeping additional metadata in your code.**

Why? Well the main reason is that *your Django application code is ultimately going to be coupled with this  data in some way, so you might as well do it all that way.* 

Let's look at this by example. In the second pricing page above there's a feature called "Ludicrous Mode" that should only be available on a Premium subscription.

One thing we needed to do is show "Ludicrous Mode" on the pricing page. That could be done easily with all three options above.

But, we also want to have logic that only allows our users to enter Ludicrous Mode if they are subscribed to the right plan.

Unless the entire permission matrix of your application lives in a database (and good luck with that if it does)&mdash;you'll end up with code like the following:

```python
if ludicrous_mode_enabled():
  do_ludicrous_stuff()
```

**So invariably your code will be coupled with your pricing tiers, anyway.** Therefore, might as well commit to maintaining this logic in code and then at least there's fewer ways for your code and data to get out of sync.

![Ludicrous Mode](https://www.saaspegasus.com/static/images/web/django-stripe-integration/ludicrous-speed.jpg)

<figcaption>
"We'll have to go right to ludicrous speed."
</figcaption>

Keeping this logic in your code also makes it easier to keep your different environments in sync, write automated tests for feature-gating logic, and roll-out and (and rollback) changes to production.

There cons of this setup&mdash;the largest being that it require developers and a deploy to production to make any changes&mdash;but by and large we've found it to be the simplest and easiest to maintain for small-to-medium sized applications and teams.

So we're going to add some code to augment our Stripe Product data.  Here's what that might look like:

```python
@attr.s
class ProductMetadata(object):
    """
    Metadata for a Stripe product.
    """
    stripe_id = attr.ib()
    name = attr.ib()
    features = attr.ib(type=list)
    description = attr.ib(default='')
    is_default = attr.ib(type=bool, default=False)


PREMIUM = ProductMetadata(
    stripe_id='prod_GqvWupK96UxUaG',
    name='Premium',
    description='For small businesses and teams',
    is_default=False,
    features=[
        features.UNLIMITED_WIDGETS,
        features.LUDICROUS_MODE,
        features.PRIORITY_SUPPORT,
    ],
)
# other plans go here
```
<figcaption>
Example of attaching metadata to your Stripe Products in code. This example uses the excellent Python <a href="https://www.attrs.org/">attrs library</a>.
</figcaption>

In the above example, we've created a metadata class to associate with a Stripe product and manually linked it by `stripe_id`. We've added attributes for our description, list of features and (and any other information we want) entirely in code, which allows us to more easily test, version control, and roll out changes that are specific to our application.

We can use this metadata structure to build out [our new pricing page](https://www.saaspegasus.com/subscriptions/).

Here's a sketch of the unstyled HTML for that page, assuming that each of your stripe products has a `.metadata` property referencing the class above.

```html
{% raw %}
<div class="plan-interval-selector">
  {% for plan in plans %}
    <button class="button">{{ plan.name }}</button>
  {% endfor %}
</div>
<div class="columns plan-selector">
{% for product in products %}
  <div class="column">
    <div {% if product.metadata.is_default %}class="is-selected"{% endif %}>
      <span class="icon">
        <i class="fa {% if product.metadata.is_default %}fa-check{% else %}fa-circle{% endif %}">
        </i>
      </span>
      <p class="plan-name">{{ product.metadata.name }}</p>
      <p class="plan-description">{{ product.metadata.description }}</p>
      <div class="plan-price">
        <span class="price">{{ product.metadata.monthly_price }}</span> / month
      </div>
      <ul class="features">
         {% for feature in product.metadata.features %}
         <li>
           <span class="icon"><i class="fa fa-check"></i></span>
           <span class="feature">{{ feature }}</span>
         </li>
         {% endfor %}
      </ul>
    </div>
  </div>
{% endfor %}
</div>
{% endraw %}  
```
<figcaption>
Richer pricing page example using a <code>ProductMetadata</code> class attached to Stripe data.
</figcaption>

This exercise of styling the HTML and making it interactive is left up to the reader.

**For a complete working example, check out [SaaS Pegasus&mdash;the Django SaaS Boilerplate](https://www.saaspegasus.com/).**

## Setting up your Subscription Data Models

Phew! Ok, now we've got our Stripe Product and Plan data synchronized with our application and we are using the data&mdash;along with some additional metadata&mdash;to generate our grid of pricing tiers. It's a good start, but we still haven't done anything to allow our users to purchase and use our Products and Plans. So let's get into that.

The first thing we'll want to do is set up the data models. And like the Products and Plans, we'll follow the same basic principle of making Stripe the source of truth, and then mapping the Stripe data to our application models.  Once again we'll take advantage of `dj-stripe` to handle a lot of the data synchronization.

The basic plan will be:

1. A user goes through the subscription workflow on our site
1. We create a subscription object in Stripe
1. We sync that subscription to our application database
1. Finally, we attach the subscription to our local data models (e.g. the logged-in acccount)

We'll cover steps 1-3 in depth when we go over [creating your first subscription](#creating-your-first-subscription), but first we're going to discuss data modeling.

### Choosing how to model Subscriptions in your Django application

As we [mentioned above](#the-stripe-billing-models-well-be-using), we'll be focusing on the  [Subscription](https://stripe.com/docs/api/subscriptions) and [Customer](https://stripe.com/docs/api/customers/object) Stripe objects.

So let's assume we already have these objects synchronized to our database. How do these fit in to our application?

To decide this we'll have to answer two basic, but important questions:

1. **What is the unit of data in our application that should be associated with the Subscription object?**    The answer to this is typically, the primary unit that is associated with *the tier itself*. 
2. **What is the unit of data in our application that should be associated with the Customer object?**    For this one, the right data model is typically associated with *how the tier is managed*. 

The choice of how to manage these will often be application-specific, though there are a few common use-cases we can cover.

### A user-based SaaS (typically B2C)

In a user-based SaaS each person has their own account and manages their own subscription. This is the most common model for business-to-consumer (B2C) apps like Spotify or Netflix (ignoring family plans).

For user-based SaaS applications the answer is likely that the Django `User` model is the right place to associate both your Subscription and Customer details. Going back to the criteria above, the `User` is associated with the subscription tier, and manages it too.

Assuming you have overridden the `User` model (which is highly recommended), that would look something like this:

```python
class CustomUser(AbstractUser):
    customer = models.ForeignKey(
        'djstripe.Customer', null=True, blank=True, 
        help_text=_("The user's Stripe Customer object, if it exists")
    )
    subscription = models.ForeignKey(
        'djstripe.Subscription', null=True, blank=True, 
        help_text=_("The user's Stripe Subscription object, if it exists")
    )
```
<figcaption>
Associating the Subscription and Customer with a <code>CustomUser</code> model, for a typical B2C SaaS application 
</figcaption>

### A team-based SaaS (typically B2B)

Most SaaS applications are actually not consumer-facing, but instead target other businesses. For a business-to-business (B2B) SaaS it's more likely that you'll have the concept of "Teams" or "Organizations" that contain multiple users&mdash;typically mapping to a company or division of a company.

In this case you likely want to associate the Subscription with the *Team* model (or whatever you've named it), because that's the unit that the tier "belongs to".

That might look like this:

```python
class Team(models.Model):
    """
    A Team, with members.
    """
    team_name = models.CharField(max_length=100)
    members = models.ManyToManyField(
        settings.AUTH_USER_MODEL, related_name='teams', through='Membership'
    )
    subscription = models.ForeignKey(
        'djstripe.Subscription', null=True, blank=True,
        help_text=_("The team's Stripe Subscription object, if it exists")
    )
```
<figcaption>
  Associating the Subscription with a <code>Team</code> model, for a typical B2B SaaS.   In this case all members of the team would have their subscription associated through the <code>Team</code>. 
</figcaption>

Okay, that makes sense, but in this case where should the Customer association go?

Well, you probably don't want *everyone* in the Team to be able to modify/cancel the Subscription. That's likely something that only someone who's an administrator of some kind should be able to do.

Once again, there are a couple options.

The simplest one is to associate the Customer with the `User` object again. This often works, although can create problems in the rare case where someone is using the same `User` account and administering multiple Teams.

Often, a better option is to use the [*through* model](https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ManyToManyField.through) to associate this information with the `Team` membership. In the `Team` example above you can see this on the `members` field.

That `Membership` model then might look something like this:

```python
class Membership(models.Model):
    """
    A user's team membership
    """
    team = models.ForeignKey(Team, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    role = models.CharField(max_length=100, choices=roles.ROLE_CHOICES)
    customer = models.ForeignKey(
        'djstripe.Customer', null=True, blank=True,
        help_text=_("The member's Stripe Customer object for this team, if it exists")
    )
```
<figcaption>
  Using a <a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ManyToManyField.through">through model</a> 
  to create a Customer association with a Team's membership.
</figcaption>

### Other models and key takeaways

Your application may require even more complicated set ups than this. For example, allowing multiple people to manage a Team's billing would probably have to be associated with a *Role object* of some kind. Or going back to Spotify&mdash;the Subscription might need to be optionally associated with a User *or* a "Team" (in the case of a family plan).

Ultimately, how you set this up is up to the details of your own application's data models, but the key takeaway is to associate the Subscription and Customer objects with the right application models according to this general principle:

<section class="notification is-info is-light" >
The Subscription object should be attached to the data model associated <em>directly with the pricing tier</em> and the  Customer object should be associated with the data model associated with <em>who manages the pricing tier</em>.
</section>

If you follow that rule you should be good. Also, this guide recommends *starting with the simplest model that works, and only expanding it when needed*. You know, [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it) and all.  

For the rest of this example we'll use the B2C use case where the Subscription and the Customer are both attached to our custom Django `User` model.

## Creating your first Subscription

Now that we've figured out how to model our data we can finally wire everything up. Time to finally get paid!

Thankfully Stripe already [provides an incredible guide](https://stripe.com/docs/billing/subscriptions/set-up-subscription) on setting up a Subscription that handles a lot of the details for us. This section will walk through that guide, providing details specific to our Django project, and focusing mostly on the sections tagged "server-side".

We've already completed Step 1 by creating our Products and Plans above.

For Step 2, the Stripe Python package should have been installed already via the `dj-stripe` dependency&mdash;though if you use a requirements file it's good to explicitly add the `stripe` package there since it is a true dependency.

Next, follow *[Step 3](https://stripe.com/docs/billing/subscriptions/cards#web-collect-card-details)* and  *[Step 4](https://stripe.com/docs/billing/subscriptions/cards#save-card-details)* of the Stripe guide to  collect and save card details on the front-end.

These steps do not have any backend-specific dependency and you can follow the instructions from Stripe basically as-is. However, *we recommend (and assume) that the email you pass to Stripe is the same as the logged-in `User`'s email, which you can grab in the Django template using `{% raw %}{{ user.email }}{% endraw %}`*.

### Submitting payment information to your server (client-side)

*This corresponds to Stripe's [Step 5](https://stripe.com/docs/billing/subscriptions/cards#web-send-to-server).*

Assuming you've completed Steps 1-4, it's time to submit the newly created payment information to our server from the front end. We'll build off Stripe's example, but make a few changes.

Here's the basic JavaScript code&mdash;with the assumption that it has been written in the context of a Django template.

```javascript
{% raw %}
  const createCustomerUrl = "{% url 'subscriptions:create_subscription";
  function stripePaymentMethodHandler(result, email) {
    if (result.error) {
      // Show error in payment form
    } else {
      const paymentParams = {
          email: email,
          plan_id: getSelectedPlanId(),
          payment_method: result.paymentMethod.id,
      };
      fetch(createCustomerUrl, {
        method: 'post',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRFToken': getCookie('csrftoken'),
        },
        credentials: 'same-origin',
        body: JSON.stringify(paymentParams),
      }).then(function(response) {
        return response.json(); 
      }).then(function(result) {
        // todo: check and process subscription status based on the response
      }).catch(function (error) {
        // more error handling
      });
    }
  };
{% endraw %}
```

Let's walk through this in detail.

First we grab the URL of the customer/subscription creation URL that we're going to submit the data to using the Django `{% raw %}{% url %}{% endraw %}` tag.


```javascript
{% raw %}
  const createCustomerUrl = "{% url 'subscriptions:create_subscription";
{% endraw %}
```

We'll define this URL in the next step, but for now just assume it exists.

Next we define the `stripePaymentMethodHandler` function and error handling. This bit is the same as the Stripe guide.

```javascript
function stripePaymentMethodHandler(result, email) {
    if (result.error) {
      // Show error in payment form
    } else {
      // create customer and subscription
    }
```
Then it's time to create the customer&mdash;via a `POST` request to our backend. 

Here we're going to make a few changes to the Stripe example&mdash;bringing in Django best-practices, and modifying the code to allow creating the Customer and Subscription object in a single request.

Here's the code.

```javascript
      // create customer and subscription
      const paymentParams = {
          email: email,  // 1. assumed to be the logged-in user's email from Step 4
          plan_id: getSelectedPlanId(),  // 2. get the selected plan ID from your DOM / state
          payment_method: result.paymentMethod.id,
      };
      fetch(createCustomerUrl, {  // 3. use the url variable we defined earlier
        method: 'post',
        headers: {
          'Content-Type': 'application/json',
          'X-CSRFToken': getCookie('csrftoken'),  // 4. CSRF validation support
        },
        credentials: 'same-origin',
        body: JSON.stringify(paymentParams),
      }).then(function(response) {
        // processing and error handling
      });      
``` 

A few notable changes that have been made from the Stripe version:

1. We've continued the assumption that the Stripe subscription was set up with the logged-in User's email.
1. Since we're also going to create the Subscription in this request, we need to pass a `plan_id` to our backend     (to know what plan to subscribe the customer to).     In this example we've made the assumption that the `plan_id` is available from the    DOM / state of your application and abstracted those details to the `getSelectedPlanId` helper function.     This would be implemented by you according to how you've structured your pricing page.
1. Rather than hard-coding it, we've grabbed our `createCustomerUrl` from     the variable set by the `{% raw %}{% url %}{% endraw %}` tag above. 1. We've added the `X-CSRFToken` header by pulling the cookie from Django's     [built-in Cross-Site Request Forgery (CSRF) protection](https://docs.djangoproject.com/en/3.0/ref/csrf/).    The [Django docs on CSRF protection](https://docs.djangoproject.com/en/3.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-and-csrf-cookie-httponly-are-false)    have more details on this approach, as well as the source of the `getCookie` function.   

Ok, that wraps the front-end bits required to create the Customer and Subscription.

Now onto the backend!

### Creating the Customer and Subscription Objects (server-side)

*This corresponds to Stripe's [Step 6](https://stripe.com/docs/billing/subscriptions/cards#create-customer-code) and  [Step 7](https://stripe.com/docs/billing/subscriptions/cards#create-subscription-code).*

Here we'll define the backend views to create the Customer and Subscription objects, as well as associate them with the application models we chose above.

First we'll first create a URL for the endpoint.

In your app's `urls.py`:

```python
from django.urls import path
from . import views

app_name = 'subscriptions'
urlpatterns = [
    # other URLs go here
    path('create_subscription/', views.create_customer_and_subscription, 
         name='create_subscription'
    ),
]
```

The `app_name` and url `name` should match what we used in the `{% raw %}{% url %}{% endraw %}` tag on the front-end.

Next we'll create the view in `views.py`. There's a lot going on here so we'll walk through it in detail but here's the complete view to start.

```python
@login_required
@require_POST
@transaction.atomic
def create_customer_and_subscription(request):
    """
    Create a Stripe Customer and Subscription object and map them onto the User object
    Expects the inbound POST data to look something like this:
    {
        'email': 'cory@saaspegasus.com',
        'payment_method': 'pm_1GGgzaIXTEadrB0y0tthO3UH',
        'plan_id': 'plan_GqvXkzAvxlF0wR',
    }
    """
    # parse request, extract details, and verify assumptions
    request_body = json.loads(request.body.decode('utf-8'))
    email = request_body['email']
    assert request.user.email == email  
    payment_method = request_body['payment_method']
    plan_id = request_body['plan_id']
    stripe.api_key = djstripe_settings.STRIPE_SECRET_KEY
    
    # first sync payment method to local DB to workaround 
    # https://github.com/dj-stripe/dj-stripe/issues/1125
    payment_method_obj = stripe.PaymentMethod.retrieve(payment_method)
    djstripe.models.PaymentMethod.sync_from_stripe_data(payment_method_obj)
    
    # create customer objects
    # This creates a new Customer in stripe and attaches the default PaymentMethod in one API call.
    customer = stripe.Customer.create(
      payment_method=payment_method,
      email=email,
      invoice_settings={
        'default_payment_method': payment_method,
      },
    )
    djstripe_customer = djstripe.models.Customer.sync_from_stripe_data(customer)
    
    # create subscription
    subscription = stripe.Subscription.create(
      customer=customer.id,
      items=[
        {
          'plan': plan_id,
        },
      ],
      expand=['latest_invoice.payment_intent'],
    )
    djstripe_subscription = djstripe.models.Subscription.sync_from_stripe_data(subscription)
    
    # associate customer and subscription with the user
    request.user.customer = djstripe_customer
    request.user.subscription = djstripe_subscription
    request.user.save()
    
    # return information back to the front end
    data = {
        'customer': customer,
        'subscription': subscription
    }
    return JsonResponse(
        data=data,
    )
```

Ok, let's walk through that in sections, starting with the declaration:

```python
@login_required
@require_POST
@transaction.atomic
def create_customer(request):
    # body
```

You can see we are requiring a login for this view. That's because we are going to use the logged-in user to determine who associate the subscription with. We're also requiring a `POST` since that's what the front-end API should always do, and we're wrapping everything in an `@atomic` transaction, so we don't end up with our database in a partially-committed state. 

Next we extract the data from the body of the POST and validate our assumptions:

```python
    request_body = json.loads(request.body.decode('utf-8'))
    email = request_body['email']
    assert request.user.email == email  
    payment_method = request_body['payment_method']
    plan_id = request_body['plan_id']
```

In the last step we passed the `POST` data to the backend as a JSON stringified set of data, so we extract that and then pull out the individual parameters. We also double-check our assumption that the email from the form match the logged-in User.

<section class="notification is-info is-light" >
  Note: It's not required that the email in Stripe match the User's email, but it will make    searching for data in the Stripe Dashboard easier.
</section>

Next we initialize Stripe with the `api_key` from our `settings.py`.

```python
    stripe.api_key = djstripe_settings.STRIPE_SECRET_KEY
```

We then sync the `PaymentMethod` object locally (note, this is just to workaround  [this bug in dj-stripe](https://github.com/dj-stripe/dj-stripe/issues/1125)).

```python
    payment_method_obj = stripe.PaymentMethod.retrieve(payment_method)
    djstripe.models.PaymentMethod.sync_from_stripe_data(payment_method_obj)
```

Next we create the `Customer` object in Stripe, using the Python API. This part is similar to what's found in [*Step 6*](https://stripe.com/docs/billing/subscriptions/cards#create-customer-code) of the Stripe guide.

```python
    # Create a new Customer in stripe and attach the default PaymentMethod in one API call.
    customer = stripe.Customer.create(
      payment_method=payment_method,
      email=email,
      invoice_settings={
        'default_payment_method': payment_method,
      },
    )
```

Now the customer has now been created *in Stripe*, but we still need to sync it to our local application DB which we can do with this line using the `sync_from_stripe_data` helper function available on every `dj-stripe` model.

```python
    djstripe_customer = djstripe.models.Customer.sync_from_stripe_data(customer)
```

Now that we have a Customer we can create and sync the Subscription object in a similar way.

```python
    # create subscription
    subscription = stripe.Subscription.create(
      customer=customer.id,
      items=[
        {
          'plan': plan_id,
        },
      ],
      expand=['latest_invoice.payment_intent'],
    )
    
    # and sync it to our application DB
    djstripe_subscription = djstripe.models.Subscription.sync_from_stripe_data(subscription)
```

And then we can finally associate the new Customer and Subscription objects with our logged in `User`.

```python
    request.user.customer = djstripe_customer
    request.user.subscription = djstripe_subscription
    request.user.save()
```

With that done, all that's left is to return some data to the front-end so that the next steps can be taken.

```python
    data = {
        'customer': customer,
        'subscription': subscription
    }
    return JsonResponse(
        data=data,
    )
```

If everything went well, our first Subscription should be created and associated with our logged in User!

### Managing the Subscription Status (client-side)

*This corresponds to [Stripe's Step 8](https://stripe.com/docs/billing/subscriptions/set-up-subscription#manage-sub-status).*

At this point we've created the `Customer` and `Subscription` objects and associated them with the appropriate `User`. So what's left?

Well, unfortunately, we aren't yet guaranteed that they've been created *successfully*. In many cases&mdash;the most common being a [3D-Secure workflow](https://en.wikipedia.org/wiki/3-D_Secure)&mdash;there  will be additional authentication steps required.

This part can follow the Stripe guide almost verbatim.  Just insert the code from Stripe's guide into your front-end above where we wrote this.

```javascript
// todo: check and process subscription status based on the response
```

The only thing you'll need to modify is extracting the `subscription` variable details from the backend response. That looks something like this.

```javascript
  const subscription = result.subscription;
  const { latest_invoice } = subscription;
  // continue with the rest of the Stripe example code
```

If you've made it this far you just created your very first Subscription. Congrats!

That's it for the current version of this guide. However, in the future I plan to flesh out additional sections on using Webhooks to keep Subscription data in sync, and working with Subscription objects in your Django application.

If there's any other content you'd like to see added, please reach out to  cory@saaspegasus.com and let me know!

For a head start building your Django SaaS application check out 
[SaaS Pegasus&mdash;the Django SaaS Boilerplate](https://www.saaspegasus.com/) which comes with subscriptions, teams and a whole lot more right out of the box!

*This guide was originally published [on saaspegasus.com](https://www.saaspegasus.com/guides/django-stripe-integrate/)*