
Using Stripe for recurring monthly SaaS subscriptions in a Django + Vue application
Last updated January 2, 2021
Using Stripe for recurring monthly payments to a paid SaaS subscription in a Django + Vue.js application
This diagram shows the flow of data for the lifecycle of a paid customer subscription in a Django application with a Vue.js client. There are four stages:
I. Account setup, configuration, model and object creation II. Logic and data flow for starting a customer's premium monthly subscription III. Automatic subscription renewal IV. Cancelling a premium subscription
Context
This is my first attempt at using Stripe, or any other online payment service API. Most of what I have diagramed here comes from this article from the Stripe documentation: https://stripe.com/docs/billing/subscriptions/fixed-price. Knowing almost nothing about what is needed to create a SaaS subscription, I found this article very helpful. It was a lot to read at once, but each call to to the Stripe API is very clear and straightforward.
I made some modifications and additions to this walk-through for my use case, which is an API service called Open SEC Data, an open source project that I'm working on (https://gitlab.com/briancaffey/sec-filings-app).
Diagram
Here's a read-only link to the diagram: https://drive.google.com/file/d/1oH2b0W-c-dI5oXzc_jvCGvXx9sJagr4a/view?usp=sharing. This diagram is made with https://www.diagrams.net/.

Legend
Here's a detailed description of each part of the diagram, starting with the first section.
I. Account setup, configuration, model and object creation
- Setup a Stripe account. For local development, make sure you turn on View test data. On your local machine, install the stripe CLI and authenticate with your Stripe account
- Create a Product in Stripe (mine is called Open SEC Data Premium Subscription)
- Create a Price in Stripe that references the Product.
 Instead of creating these objects in the Stripe Dashboard, you can also create them with the Stripe CLI or the Python SDK. I created a Django management command calledcreate_stripe_datathat will create a Product and related Price in Stripe. We will need the id of the Price, it looks like this:price_1Hx0goL67dRDwyuDh9yEWsBo.
- Add the Price ID as an environment variable SUBSCRIPTION_PRICE_IDto the backend. This will be used later when we make API calls to Stripe from inside of Django views.
- For production environments, you will need to a register a Stripe webhook. This is an endpoint in Django that Stripe will POST to in order to inform the Django application of events that have happened in Stripe.
 For local development we need to runstripe listen --forward-to localhost/api/stripe-webhooks/in order to forward webhook events to the local Django application. This works really well for local development.
- In both local and production environments we need to add an environment variables to the Django application that will be used to validate the webhook event.
- You will need to create a Subscriptionmodel or similar in your Django models. This model should be related to your user model in some way. At a minimum it should have thesubscription_id(the ID of the Stripe Subscription) andcurrent_period_end(also from the Stripe Subscription object). We will use this model in the next sections.
- /api/stripe-webhooks/is the endpoint in the Django application that Stripe will send POST requests to in order to inform the Django application of events that happen in Stripe. The URL can be called anything you want, as long as you register it with that URL. In local development, you need to specify this URL in the- stipe listencommand (for example,- stripe listen forward-to localhost/api/stripe-webhooks/).
- STRIPE_SECRET_KEYis the name of the secret API key that should only be accessible by the backend. In local development, this key looks like- sk_test_Abc123. In production, this key will look like- sk_Abc123.
- stripeis the name of the PyPI package that we need to add to- requirements.txt(- requirements/base.txt).
- STRIPE_PUBLISHABLE_KEYis the value of the Stripe API key that can be made public and is used in the Vue application to instantiate Stripe.
- The Stripe library is included in index.htmlvia CDN so that it is accessible anywhere in the Vue application. Stripe object is instantiated in the Vue application with:let stripe = Stripe(process.env.STRIPE_PUBLISHABLE_KEY)
II. Logic and data flow for starting a customer's premium monthly subscription
With everything setup and configured properly in Stripe, the backend Django application and the frontend Vue application, customers can now start paying for monthly subscriptions. In my application, a user can sign up for an account first without having a premium subscription. In other scenarios, having an active account may require a premium subscription.
- When a logged-in user visits their /accountpage, they will see the status of their account: Basic (free) or Premium (paid subscription). Users on a Basic plan will see the option to upgrade to Premium. They will be redirected to a/premiumpage where they will be presented with a credit card form. This credit card form is generated by Stripe Elements. The user fills out their credit card, expiration date, card security code and billing ZIP code and then clicksPurchase.
- Clicking on Purchasecalls a methodpurchasethat callsstripe.CreatePaymentMethod. ThepaymentMethodIdtoken returned fromstripe.CreatePaymentMethodis then passed to the method calledcreateSubscription.
- Stripe creates this object and returns a response that contains a paymentMethodIdtoken.
- createSubscriptionsends a POST request to- /api/stripe/create-subscription/in the Django application with the- paymentMethodIdthat we generated in the previous step.
- /api/stripe/create-subscription/calls a view called- create_subscriptionwhich makes a number of API calls to Stripe and then finally saves some data in the application's Postgres database.
- The first API call creates the Customer object in Stripe if it does not exist. email=request.user.emailis used in the API call to associate the Stripe customer with the user's email.
- Next the payment method is attached to Stripe Customer model.
- Next the Stripe payment method is set as the default payment method for Stripe customer for future billing.
- The Stripe subscription model is created with the customer ID that was created in the earlier and the price ID corresponding to the premium subscription (added in the setup stage).
- Once these Stripe API calls have finished, a new Subscription is saved in the Postgres database. stripe_subscription_id,stripe_customer_idandvalid_through(DateTimeField that keeps track of the date through which the user's subscription has been paid for) are saved to theSubscriptionmodel and then the subscription model is saved to the user model'ssubscriptionfield.
- When the createSubscriptionmethod's POST requests returns successfully, the user's account is fetched again from/api/account/
- The browser makes a request to /api/account/.
- /api/account/returns information on the user and their subscription.
- Data from /api/account/is updated in Vuexuserstore.
- The user is now able to make requests to resources for premium features.
- In this application, one such example is the ability to request an API key for making for making API calls.
- A user makes a request to an API endpoint for a premium feature.
- When determining permissions for resources that should only be accessible to customers with valid subscriptions, we need to compare request.user.subscription.valid_throughtotimezone.now()and make sure thatvalid_throughis greater thantimezone.now().
- Requests for protected resources are successfully returned to the browser.
III. Automatic subscription renewal
The customer's credit card is charged once each month that they are subscribed to the service. This action happens in Stripe. This section assumes that the customer's primary payment method is still valid (it has not been canceled expired or not able to be charged for some other reason).
- The customer's card is charged in Stripe and an event is sent to the Django backend via a webhook that we registered in the setup stage.
- The webhook view checks event.typeand if the event is of typeinvoice.paidwe extend the user's subscription by one month.
- To extend the user's subscription, we modify the DateTimeFieldfield on theSubscriptionthat tracks thecurrent_period_endwhich is included in the webhook data object. The model field in my code is calledvalid_through.
IV. Cancelling a premium subscription
- When a user decides to cancel their payed subscription service, they click on the Cancel My Subscriptionbutton.
- This makes a POST request to /api/stripe/cancel-subscriptionwhich calls thecancel_subscriptionview. This view callsstripe.Subscription.delete(subscriptionId), where thesubscriptionIdis retrieved fromrequest.user.subscription(theSubscriptionmodel created in the setup section).
- The subscription is deleted in Stripe through the stripe.Subscription.deleteAPI call.
- The user's subscription is deleted from the user model with request.user.subscription.delete().
- The frontend responds to the deleted subscription by fetching /api/account/again, refresh, or redirecting and the user no longer has access to their premium subscription.