Library / SDK
djaodjin/djaodjin-saas avatar
djaodjin/djaodjin-saas

djaodjin-saas: the subscription billing layer for a Django SaaS

Django application for software-as-service and subscription businesses

603 stars135 forksPythonNOASSERTION

At a glance

What is it?
djaodjin-saas is a Django application that supplies the plan, subscription and double entry ledger logic behind a subscription business, with Stripe and Razorpay backends and separate billing profiles from authenticated users. It ships as a reusable app, not a finished product.
Who is it for?
Adopt djaodjin-saas when you are building a Django product with recurring plans and need a real ledger behind the money, and skip it when you need a hosted billing service, since this is a library you must integrate, template and operate yourself.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

It implements billing logic, not a billing service

The name is easy to misread. djaodjin-saas is described as a Django application that implements the logic to support subscription-based Software-as-a-Service businesses. It is not a hosted payment product, and it is not a complete site.

The three major features listed are separate billing profiles and authenticated users, a double entry book keeping ledger, and a flexible security framework. The first one is a modelling decision that shapes everything else. A billing profile is not a user account. It carries the billing details, and it can exist independently of who is signed in, which is what lets one organisation have several paying entities and lets a customer pay for someone else. The second feature is the one most subscription projects get wrong. A double entry ledger means every movement of money produces balanced journal entries, so the question of how much a customer has paid is answered by the books rather than by counting rows in a payments table.

The audience is a Django developer who already has an application and needs the money part of it. The repository topics list django, plans, stripe, razorpay, subscriptions and saas, which is a compact statement of what is provided.

One thing to set expectations on straight away: the project ships bare bone templates compatible with both the Django and Jinja2 template engines. Bare bone means you will be replacing them.

Payment backends, with one dependency pinned hard

The payment integration is the layer that touches a third party, and it is where the dependency list says the most.

Stripe is pinned to an exact version, stripe==12.5.1, with no range at all, while Razorpay is looser at razorpay>=0.2.0. That asymmetry is informative. A pinned Stripe version is a deliberate choice for a billing integration, because the Stripe API surface changes and an unpinned client would let a library upgrade silently change the behaviour of your payment code path. The trade is that you own the upgrade: bumping that pin means re-testing the checkout flow.

The rest of the dependency list is Django-shaped infrastructure. Django itself at 3.2 or newer, django-restframework at 3.3.1 or newer, django-countries, django-localflavor and django-phonenumber-field with the phonenumbers library underneath for international phone handling, Markdown for django.contrib.markup, and python-dateutil for date arithmetic. The geography and phone packages are not incidental: a subscription product that charges in more than one country needs country and phone validation at signup, and djaodjin-saas bakes those fields into its own models rather than leaving you to add them.

The documented processor setup lives in a Processor Backends page, and the development instructions give Stripe as the worked example with a credentials file holding a secret key and a Stripe public and private key.

The testsite is the tutorial, and it is a real Django site

The repository contains a testsite/ directory that is a working Django project, and the development instructions walk through it in the order you would actually need.

The first block creates a virtualenv and installs the test site's requirements, then pulls in the JavaScript prerequisites needed for the browser side.

bash
    $ python -m venv .venv
    $ source .venv/bin/activate
    $ pip install -r testsite/requirements.txt

    # Installs Javascript prerequisites to run in the browser
    $ make vendor-assets-prerequisites

Next you write a credentials file. The instructions are to add the payment processor keys and the Django secret key, and they show the shape with Stripe.

bash
    $ cat ./credentials

    SECRET_KEY = "<em>enough_random_data</em>"
    STRIPE_PUB_KEY = "<em>your_stripe_public_api_key</em>"
    STRIPE_PRIV_KEY = "<em>your_stripe_private_api_key</em>"

Then the database gets migrated, the initial fixtures are loaded, and a superuser is created. Dummy transaction data can be generated afterwards with a dedicated management command, which is the fastest way to see the ledger populated.

bash
    $ python ./manage.py migrate --run-syncdb --noinput
    $ python ./manage.py loaddata testsite/fixtures/initial_data.json
    $ python ./manage.py createsuperuser
bash
    $ python ./manage.py load_test_transactions

Finally the server runs and the site is browsed on the standard Django development port.

bash
    $ python manage.py runserver

    # Browse http://localhost:8000/

The Makefile at the repository root backs this up with targets for install, install-conf, dist and cleaning, and it drives the same manage.py through a MANAGE variable that points at the config directory and run directory. The dist target builds with python -m build and then runs twine check and twine upload, which is the release path for the package itself.

Rest framework pagination and the signals based email model

Two implementation notes in the documentation save real debugging time, and both are consequences of choices rather than of bugs.

The first is pagination. The note says that the latest versions of django-restframework, 3.0 and up, implement paginators disconnected from parameters in views, meaning no more paginate_by, so you have to define PAGE_SIZE in your settings.py yourself.

bash
    $ diff testsite/settings.py
    +REST_FRAMEWORK = {
    +    'PAGE_SIZE': 25,
    +    'DEFAULT_PAGINATION_CLASS':
    +        'rest_framework.pagination.PageNumberPagination',
    +}

The second is email. The application does not send notification e-mails itself. Instead, all major updates that would result in an email trigger signals, and it is straightforward to send e-mails on a signal trigger in the main Django project. Sample e-mail templates are provided in the saas/templates/notification/ directory.

That is a deliberate separation. The library decides what happened, the host project decides how a customer hears about it. If you want every receipt, password reset and subscription notice to go through your own mail infrastructure with your own branding and your own retry policy, this design fits. If you expected the package to handle delivery out of the box, you have to write that part, and the templates are a starting point rather than a finished mailing system.

A third, smaller note covers the plan and profile model. The release notes for 1.3.1 mention generating profile page form fields automatically for JSON field target keys, enabling search and ordering on all models based on a target key in a JSON field, and allowing a profile primary address to be an e-mail or an e-mail domain. That JSON target key idea is what lets a plan define extra custom fields without a schema migration.

Version support, and a SQLite trap worth knowing about

The release notes list the combinations the project is tested with: Python 3.12 with Django 5.2 as the LTS, Python 3.14 with Django 6.0, and Python 3.10 with Django 4.2 and Python 3.9 with Django 3.2 both marked legacy. The pyproject.toml sets requires-python at 3.9 or newer and the Django dependency at 3.2 or newer, which lines up with that range.

Two version facts matter for planning. The 1.3.1 notes are the most recent set in the repository, and the project has no GitHub releases, so the changelog file at the repository root is where version history lives. The last push was on 2026-09-16, and the repository is not archived, so the code is being worked on even though it is not versioned through GitHub releases.

The SQLite trap is worth quoting because it is the kind of thing that costs an afternoon. If you create your own fixtures file and try to load it with a Django version before 2 while the Python executable is linked with a SQLite version after 3.25, you can hit the SQLite 3.26 bug that breaks database migration ForeignKey constraints and leaves a table_name__old entry in the database schema. The advice given is to use Django 2 or newer, or delete the migrations/ directory.

Note that this applies to your own fixtures, which is exactly the case when you load a plan or product catalogue that you authored. The bundled testsite fixtures are the safer path.

The licence needs reading, and the packaging metadata disagrees with the repository field

There is a licence detail here that deserves a careful look rather than a skim, and it is not a legal opinion.

The pyproject.toml declares the licence as BSD-2-Clause, and the header in setup.py is a two-clause BSD style redistribution notice, with the standard conditions on retaining the copyright notice and the disclaimer of warranty and liability. The repository licence field, however, lists the licence as Other, described as a custom licence GitHub cannot classify, and points at the LICENSE file. The LICENSE file in the tree is named LICENSE.txt.

That discrepancy is the kind of thing that gets resolved by opening LICENSE.txt rather than by assuming the classifier is right. If you plan to vendor the app into a proprietary product, that file is the one to read, and the BSD text in the source headers is a starting point rather than a guarantee.

The other adoption consideration is scale. This is a mature, opinionated application, not a small utility. The security framework is described as flexible, which means there is a configuration surface to understand, the templates need replacing, and the notification emails need wiring. The companion project djaoapp is described as a full-fledged subscription-based session proxy that uses djaodjin-saas, so it is the reference for what a finished product on top of this looks like.

The documentation is on Read-the-Docs, and the getting-started page covers setting up a SaaS site, which is where the data model gets explained in more detail than the README does.

Editorial conclusion

Adopt djaodjin-saas when you are building a Django product with recurring plans and need a real ledger behind the money, and skip it when you need a hosted billing service, since this is a library you must integrate, template and operate yourself. Before you commit, walk the testsite path from pip install -r testsite/requirements.txt to python manage.py runserver, decide whether the pinned stripe==12.5.1 dependency and the Django 3.2 to 6.0 support range fit your target, and read LICENSE.txt directly, because the packaging metadata declares BSD-2-Clause while the repository licence field lists a custom licence GitHub cannot classify.

Frequently asked questions

What does djaodjin-saas actually provide?

It is a Django application implementing subscription business logic: separate billing profiles from authenticated users, a double entry book keeping ledger and a flexible security framework, along with Stripe and Razorpay payment backends. It ships bare bone templates compatible with the Django and Jinja2 template engines that you are expected to replace.

How do I set up the djaodjin-saas testsite locally?

Create a virtualenv, install testsite/requirements.txt, run make vendor-assets-prerequisites for the browser assets, add a credentials file with the Django secret key and processor keys, then run migrate --run-syncdb --noinput, loaddata testsite/fixtures/initial_data.json, createsuperuser and finally python manage.py runserver.

Does djaodjin-saas send notification emails itself?

No. Major updates that would result in an email trigger signals, and you send emails on those signals from the main Django project. Sample e-mail templates are provided in the saas/templates/notification/ directory.

Which Python and Django versions does djaodjin-saas support?

The release notes list it as tested with Python 3.12 and Django 5.2, Python 3.14 with Django 6.0, and the legacy pairs Python 3.10 with Django 4.2 and Python 3.9 with Django 3.2. The pyproject.toml sets requires-python at 3.9 or newer.

Why does my fixture load fail with a table_name__old schema error?

The documented cause is a Django version before 2 paired with a SQLite version after 3.25, which triggers the SQLite 3.26 foreign key migration bug. The guidance is to use Django 2 or newer, or to delete the migrations/ directory.

Official sources

  1. djaodjin/djaodjin-saas on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/djaodjin-djaodjin-saas.svg)](https://hysenlabs.com/projects/djaodjin-djaodjin-saas)

Was this useful?