Anvil's SaaS (Software as a Service) template is a solid starting point and foundation for your subscription-based SaaS product. This template uses Stripe's API for subscription management, and includes simplified user permissions for you to use throughout your app. It's an ideal starting point for your project.
Stripe provides tools for businesses to accept, manage, and process payments online and in person, offering features like customizable APIs, fraud prevention, subscription billing, and global currency support.
In this guide, we’ll walk through the key components of the template, covering:
- Introduction: Briefly learn about Anvil and the benefits of using this template.
- Prerequisites: What you’ll need to get started.
- Template Structure: A high-level overview of the app’s architecture and Stripe integration.
- Template Setup: Step-by-step instructions to get the template up and running with your account.
- Testing The App: Test the integration and explore the template’s functionality from a user’s perspective.
- Make The app Your Own: With the integration set up, it's time to make the app your own.
If you're new here, welcome! Anvil is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just build it all in Python.
You're going to need to know the basics of Anvil before using this template, so I'd recommend following our 10-minute intro tutorial. This should give you enough knowledge to begin using the SaaS template.
This template is a solid foundation for building your own SaaS app. It gives you:
- Full Stripe payment and checkout
- Subscription management synced with the app
- Account management synced with Stripe
- Easy-to-configure user permissions
Overall, it's an ideal starting point for your project.
To follow this guide you will need the following:
- An understanding of Python
- A Stripe account
- Basic knowledge of Anvil (a great place to start is with Anvil's Feedback form tutorial)
The template is divided into two main parts: the Stripe integration and the Anvil app. Stripe manages payments, subscriptions, and invoicing, while the Anvil app handles user authentication and permissions.
The app relies on the following Stripe features:
- Customer Portals - allows our customers to self-manage their payment details, invoices, and subscriptions in one place.
- Pricing Tables - displays our prices, configured easily from our Stripe dashboard and takes users into Stripes checkout flow.
- APIs - to send data and requests to our Stripe account.
- Webhooks - to send data to our Anvil app when an event happens in Stripe.
customer.subscription.updated
customer.created
Here's an API flow to help visualise the integration:
And here's a user flow diagram to visualise the functionality available to the end user:
This section will guide you through getting started with the template, understanding its features, and further developing it to suit your needs.
Let's get started!
Start by cloning the template app with the following link: https://anvil.works/build#clone:SUTICB6NGVZM7J5S=CB3DXR7V65FYL6UQOF2VQF5L
Next, we'll set up our Stripe account. Register for a Stripe account and login. Then enter your business details to start capturing recurring revenue (or skip this step if you're only going to use Stripe's test mode). Lastly, activate Stripe's test mode.
For the integration to work, we need to add your Stripe API key to the app. You can find your keys by clicking on "Developers", on the bottom left corner of the Stripe dashboard, and going to the API keys tab.
Copy your Stripe account's Secret key and, in the SaaS template app's App Secrets, set the value of "stripe_test_api_key" to your key.
Next, we need to create a pricing table for our customers to use. Start in the Anvil app editor, publish this app and take a copy of the URL - we'll use this in later in this step.
In the Stripe dashboard, navigate to the Products catalogue, select the Pricing tables tab, and create a pricing table.
- Click on the textbox under "Products" and select "Add new product".
- Name your product 'Personal', give it a price and save it by clicking 'Add product'. Then, click 'Continue' onto the next step.
- In the Payment settings, under "Confirmation page", select "Don't show confirmation page" and enter your published app's URL.
Stripe's website should take you to your pricing table's page (if not, follow this link and select your pricing table). Now, we'll embed the pricing table in our SaaS app.
- Copy the code for the pricing table.
- Open the StripePricing Form in the Anvil Editor. Then edit the custom HTML by clicking the three dots at the top of the designer.
- Paste the code into the StripePricing Form's custom HTML.
- Lastly, add
anvil-name="stripe-pricing-table"
to thestripe-pricing-table
tag. The final HTML should look like this:
We need Stripe to tell us when a new customer is created and when their subscription is updated so we can update our Users table with the Stripe subscription details. We'll use webhooks to do this. There is a guide to setting up webhooks in Stripe here but let me give you brief instructions.
- Open the Webhooks page in Stripe by selecting the "Developers" link in the bottom left and navigating to the webhooks tab.
- Click the "Create an event destination" button.
- Set the endpoint URL to your published app's URL with "/_/api/stripe/stripe_customer_created" added to the end - i.e. "https://my-saas.anvil.app/_/api/stripe/stripe\_customer\_created".
- Then click "+ select events" and select "customer.created" under events to listen for.
- From now on, this will call the
stripe_customer_created
function in your Anvil app's StripeFunctions module when a customer is created. Here's an image of the set-up webhook:
- Add another endpoint in Stripe.
- Set the endpoint URL to your published app's URL with "/_/api/stripe/stripe_subscription_updated" added to the end i.e. "https://my-saas.anvil.app/\_/api/stripe/stripe\_subscription\_updated"
- Then select "customer.subscription.updated" under events to listen for.
- From now on, this will call the
stripe_subscription_updated
function in the StripeFunctions Server Module every time a customer is created.
Let's quickly set up a way for users to manage their subscription. Go to the Stripe dashboard and use the search bar at the top to find the customer portal screen.
Set up a customer portal, activate the test link and copy it.
Then open the SaaS app's AccountManagement form and point the "manage_subscription_link" component's URL to the copied link:
We're going to let users manage their email address from the Account page of our app, so we'll disable the ability for users to change their email in their customer portal. The SaaS template syncs email updates with Stripe automatically. Back in the customer portal config page, uncheck the "Email address" checkbox under customer information:
With these steps completed, your Stripe integration is ready for testing.
The template has a number of Notifications which will guide you through testing the app as a user. This will both test the integration we've set up and let you experience what the app is like as a user. Run the app and follow along with the in-app instruction notifications.
Here's a user flow diagram for you to see all the actions you can take with the template:
Now that your Stripe integration is set up and you've experienced the app from a user's perspective, it's time to make this app your own.
First, we can search (ctrl+shift+F) for the "# TEMPLATE EXPLANATION ONLY" comments and delete the relevant code below.
For a SaaS app to earn money, you'll want to have premium features that are restricted to paid users on specific plans - this is where user permissions come in.
The template comes with a bespoke function and decorator designed to simplify managing user permissions:
user_has_subscription
: A function that works with@anvil.server.callable
. It verifies whether the user's subscription is included in the list of subscriptions authorized to use the function decorated by@anvil.server.callable
. If permission is denied, aPermissionDenied
exception is thrown.@catch_permission_errors
: A decorator that catches anyanvil.server.PermissionDenied
exceptions raised by the decorated client-side function. If permission is denied, it prompts the user to upgrade their subscription.
You can use @anvil.server.callable
, user_has_subscription
and @catch_permission_errors
together as follows:
To restrict access to a premium function to users with a valid subscription, start by decorating the premium function with @anvil.server.callable
. You can pass @anvil.server.callable
's require_user
argument user_has_subscription
. user_has_subscription
takes a list of allowed subscriptions and checks if the logged-in user’s subscription matches any entry in the list.
From the template, this example function is only allowed to be run by users with a "personal" subscription:
# Here's an example of a function that would require a paid subscription
@anvil.server.callable(require_user=user_has_subscription(["personal"]))
def calculate_percentage_of(number, total_number):
percentage = (int(number) / int(total_number)) * 100
return percentage
If the user has an active subscription which is in the allowed subscriptions passed to user_has_subscription
, the function will run. If they don’t meet the subscription requirement, a permissions error is raised and caught by @catch_permission_errors
- let me show you how to use @catch_permission_errors
.
With the server-side function restricted, it's time to prompt users to upgrade if they try to use a premium feature without the right subscription - for this, you can use @catch_permission_errors
. @catch_permission_errors
will catch any anvil.server.PermissionDenied
exceptions thrown by our server-side function and prompt users to upgrade if they don’t meet the subscription requirement.
From the example in the template:
# Catch_permission_errors catches exceptions that are thrown by a user not being subscribed and gives them a notification to upgrade
@catch_permission_errors
# This function is a simple example function to show you functionality that is gated behind a paywall
def calculate_button_click(self, **event_args):
"""This method is called when the button is clicked"""
if self.number_1_textbox.text and self.number_2_textbox.text:
# This makes a call to a restricted function
percentage = anvil.server.call('calculate_percentage_of', self.number_1_textbox.text, self.number_2_textbox.text)
...
And that's it, you can restrict any number of features this way.
When you're ready to go live, switch Stripe from test mode to live mode.
Next, update the app's account management link (configured in step 7) to use the customer portal's live link.
Finally, replace the API keys in your Anvil app with the production keys.