These docs are a work-in-progress and are currently undergoing a major rewrite!
Forge Pro


When it's time to implement billing, Forge comes with a few things to make it even easier to integrate Stripe.

You can build your own checkout flows if you want, but these days Stripe provides a nice hosted checkout page for starting new subscriptions, and a customer portal for letting people upgrade, cancel, or update payment methods. You need a couple of server-side views to redirect people to these pages, but you don't need to build and design these things yourself.

Forge makes this even easier by providing classes you can extend.


Add forgepro.stripe to the INSTALLED_APPS:



The StripeModel class is simple, but useful. With it you will get a stripe_id field, where you will typically store a Stripe customer ID (like on a Team), or something more specific like a Stripe subscription or charge ID.

from django.db import models
from forgepro.stripe.models import StripeModel

class Project(StripeModel):
    # stripe_id will be used to tie a Stripe subscription to a project
    name = models.CharField(max_length=255)

You will then get a stripe_object cached property to make it easy to fetch the rest of the data from the Stripe API (the API key will be set for you by Forge).

You can use this in Python code:


But also in templates:

{% if project.stripe_object %}
    <p>Subscription status: {{ project.stripe_object.status }}</p>
{% else %}
    <p>No subscription</p>
{% endif %}

You can store additional Stripe data in your database if you need to, but in general we recommend fetching from the Stripe API directly unless you frequently need to query or display a specific field. It's easy to go overboard and store every product, invoice, or charge but in reality you probably don't need to. Especially if you take advantage of the hosted Stripe checkout and customer portal.

Views and URLs

There are three Stripe-related views mixins in Forge:

  • StripeCheckoutView - to create a checkout session and redirect to it (usually to start a new subscription)
  • StripePortalView - to create a customer portal and redirect to it
  • StripeWebhookView - to receive webhooks when checkout is completed or subscriptions are updated


Use StripeCheckoutView to create new subscriptions.

from django.urls import path

from . import views

app_name = "projects"

urlpatterns = [

In your templates, use a simple form so that it generates a POST request:

<form method="post" action="{% url 'projects:checkout' project.uuid %}">
    {% csrf_token %}
    <button type="submit">Start project subscription</button>

The view is where you will put your custom logic and decide which plans/products to use. You can use any info from the request itself, your settings, or database:

from forgepro.stripe.views import StripeCheckoutView

class ProjectCheckoutView(ProjectDetailMixin, StripeCheckoutView, generic.DetailView):
    def get_checkout_session_kwargs(self, request):
        project = self.get_object()

        redirect_url = request.build_absolute_uri("/")

        # The "team" will be tied to the actual customer,
        # so we'll get or create that customer now
        team =

        if team.stripe_id:
            customer = team.stripe_id
            customer = stripe.Customer.create({
                "metadata": {"team_uuid": team.uuid},
            team.stripe_id =

        return {
            "customer": customer,
            "success_url": redirect_url + "?stripe=success",
            "cancel_url": redirect_url + "?stripe=cancel",
            "mode": "subscription",
            # `client_reference_id` will come back in the webhook,
            # making it easier to look up the associated project
            "client_reference_id": project.uuid,
            "payment_method_types": ["card"],
            "allow_promotion_codes": True,
            "line_items": [
                    "price": settings.STRIPE_PRICE_ID,
                    "quantity": 1,

The price field was set in Django settings for this example, but you could easily pass it in as a request.POST field from a template, or retrieve it from some other source.

When the checkout is completed, you'll receive a webhook which you can use for "success" processing.


The StripePortalView is used to let users update payment methods, view invoices, modify their subscription, or cancel it.

Usage is very similar to StripeCheckoutView, but you do need to have an existing customer ID to use the StripePortalView.

from forgepro.stripe.views import StripePortalView

class TeamPortalView(
    BaseLoggedInViewMixin, StripePortalView, generic.DetailView
    def get_portal_session_kwargs(self, request):
        team = self.get_object()

        # Make sure to pass an absolute url to Stripe (https://...)
        return_url = request.build_absolute_uri("/")

        return {
            "customer": team.stripe_id,
            "return_url": return_url,


urlpatterns = [
    path("stripe-webhook/", views.StripeWebhook.as_view()),

In this example we are going to save a specific Stripe subscription ID to a project:

from forgepro.stripe.views import StripeWebhookView

class StripeWebhook(StripeWebhookView):
    def handle_stripe_event(self, event):
        if event.type == "checkout.session.completed":
            # client_reference_id can be set when you use StripeCheckoutView
            project_uuid =
            project = Project.objects.get(uuid=project_uuid)
            project.stripe_id =

        elif event.type == "customer.subscription.deleted":
            subscription_id =
            project = Project.objects.get(stripe_id=subscription_id)
            project.stripe_id = ""


We include two template tags that help output Stripe data:

  • epoch_to_datetime
  • decimal_to_dollars
{% load stripe %}

{{ project.stripe_object.current_period_end|epoch_to_datetime|date:"DATE_FORMAT" }}

${{ project.stripe_object.plan.amount|decimal_to_dollars }}


The easiest way to test webhooks is to install the Stripe CLI. On a Mac, you can install it with Homebrew:

brew install stripe/stripe-cli/stripe

stripe login

Then in your .env file, you can add a STRIPE_WEBHOOK_PATH (ex. STRIPE_WEBHOOK_PATH=/webhooks/stripe/) which will be detected by forge work and automatically start a stripe listen process when you run forge work:

Alternatively, you can use Stripe for VSCode or a more generic tunneling tool like Ngrok.