This application will offer a premium subscription service that users can pay for with Stripe. Premium subscriptions will give users unlimited access to the API as well as access to certain parts of the web UI that regular users do not have access to.
Our application will have the following user tiers:
- Unauthenticated guest users
- Free tier users (signed up with LinkedIn)
- Premium users (signed up with LinkedIn and payed with Stripe)
Before looking at the Stripe API, here's a brief overview of what I would like to be able to do:
- Build a view where signed-up users can purchase a Premium Plan
- API access for a signed-up user will be determined by their premium subscription status (non-active, active, cancelled)
- Users can cancel their premium subscriptions at any time
This seems like a good place to start: https://stripe.com/docs/billing/subscriptions/fixed-price.
This example has a GitHub repo with an example backend implementation in Flask that should be a good reference for doing something similar in Django.
For the frontend this repo has examples for vanilla JS as well as React. These may also be helpful for our implementation in Vue.
First we will need to setup a business in Stripe in which we can create our service.
Once we have setup the account, we will want to add the test
API keys to our .env
file. For the production keys, we can add these to the backend and NGINX Dockerfile
s using ARG
and ENV
, and we can also add them in the docker build
commands of the .gitlab-ci.yml
file.
Here's a high level overview of what we need to setup recurring billing for our users:
- A Stripe account with a business setup in Stripe.
- test API keys in local development (publishable key in frontend, secret key on backend)
- live API keys in GitLab secrets, .gitlab-ci.yml, Dockerfiles and
docker build
commands as--build-arg
s
stripe
package installed from PyPIstripe listen --forward-to localhost/api/stripe-webhooks/
command running using CLI (the key shown in the output of this command must be added as theSTRIPE_WEBHOOK_SECRET
)- django management command to create webhook used in production using webhook API.
- django management command to create prices and products
- save the price_id of the subscription
dj-stripe
to sync Stripe data with Django models- endpoint for Stripe webhooks to listen for events that happen (this can be used with dj-stripe)
- endpoint for
create_subscription
- endpoint for
cancel_subscription
- Add stripe via CDN to
index.html
:<script src="https://js.stripe.com/v3/"></script>
- Add a
Stripe.vue
component
- Instantiate
stripe
withStripe("PUBLISHABLE_KEY")
- Define
elements
asstripe.elements()
- In the mounted hook, create and mount the
card
variable - Define a
purchase
function that is called on a button click when card information is filled out and the customer is ready to purhcase - Inside the
purchase
function, callstripe.createPaymentMethod
that takes in thecard
information. - When
stripe.createPaymentMethod
completes, call our customcreateSubscription
function. - Alternatively, use a package like https://www.npmjs.com/package/vue-stripe-elements-plus to simplify some of the logic
- This function needs to take in the
paymentMethod.id
that we get from theresult
ofstripe.createPaymentMethod
- This function will
POST
to our Django backend's/api/stripe/create-subscription/
route (create_subscription
)
- This function does three things:
- get or create the Stripe Customer ID (which is stored on the user model;
request.user.stripe_customer_id
) usingstripe.Customer.create
- Attach the payment method to the customer with
stripe.PaymentMethod.attach
- Set the default payment method on the customer with
stripe.Customer.modify
- Create the subscription with
stripe.Subscription.create
- Finally, this view returns the results of
stripe.Subscription.create
(JsonResponse(subscription)
). - We may want to save additional information from the
subscription
on our User model, such as the expiration date, or update asubscription_status
flag for checking permissions when accessing gated resources.
- An event with type
invoice.paid
is sent to the webhook, - Get the update the
valid_through
field on ourSubscription
(Django) model.
- Setup and endpoint for
cancel_subscription
- Call
stripe.Subscription.delete
in this endpoint - Delete the Django model subscription with
request.user.subscription.delete()
- Test subscription renewals
- Figure out how to test things in CI, how to mock stripe library calls