Getting Started
How to Install
1. Download and install the package with pip
Get Django Paddle Subscriptions from Gumroad.
Create a directory private_wheels/
in your project's repository and add the wheel file of the app there.
Link to this file in your requirements.txt
:
Django==4.2
file:./private_wheels/django_paddle_subscriptions-1.3.2-py2.py3-none-any.whl
Install the pip requirements from the requirements.txt
file into your project's virtual environment:
(venv)$ pip install -r requirements.txt
Alternatively to start quickly, install the wheel file into your Django project's virtual environment right from the shell:
(venv)$ pip install /path/to/django_paddle_subscriptions-1.3.2-py2.py3-none-any.whl
2. Add the app to INSTALLED_APPS
of your project settings
INSTALLED_APPS = [
# ...
"paddle_subscriptions",
]
3. Add SubscriptionMiddleware
to the MIDDLEWARE
setting
MIDDLEWARE = [
# ...
"paddle_subscriptions.middleware.SubscriptionMiddleware",
]
It will allow to access the current subscriber's data at request.subscriber
.
4. Add PADDLE_SUBSCRIPTIONS
settings
PADDLE_SUBSCRIPTIONS = {
"API_KEY": "...",
"PUBLIC_KEY": "...",
"CLIENT_SIDE_TOKEN": "...",
"ENVIRONMENT": "sandbox",
"WEBSITE_URL": "https://example.com",
# ...
}
Important note! Don't commit the keys and tokens to the version control for security reasons!
Set the setting PADDLE_SUBSCRIPTIONS['RESTRICTED_TO_IPS'] = ['...']
to show pricing or other subscription-related widgets only to visitors from those IPs.
5. Add settings for PayPal
If you use PayPal as one of the alowed payment gateways, add these settings:
MIDDLEWARE = [
# ...
"django.middleware.security.SecurityMiddleware",
]
SECURE_CROSS_ORIGIN_OPENER_POLICY = "unsafe-none"
6. Add a path to urlpatterns
from django.urls import path, include
urlpatterns = [
# ...
path(
"subscriptions/",
include("paddle_subscriptions.urls", namespace="paddle_subscriptions"),
),
]
7. Create Paddle data
In your Django project, create subscription plans with benefits for pricing widgets. If you have a free plan, set the call-to-action URL name at PADDLE_SUBSCRIPTIONS['FREE_PLAN_CTA_URL']
, for example, the sign up or waitlist form.
At Paddle create Products for each paid subscription plan, and monthly and yearly prices for each product. You can skip monthly or yearly pricing if you want, but then you will need to modify the overwritten templates to exclude them there too.
Deploy the data to a publicly accessible staging or production website. Paddle will send events to that website.
8. Link Paddle with your website
Run the management command to fetch Paddle data and install the webhook:
(venv)$ python manage.py set_up_paddle_subscriptions
The webhook at https://example.com/subscriptions/webhook/
will be registered at Paddle and all Paddle events available in the API will be sent to it.
Then, link your subscription plans with sandbox monthly and yearly prices.
Also set the default payment link at Paddle (Paddle ➔ Checkout ➔ Checkout settings) to https://example.com/subscriptions/payments/
. It will redirect to the correct location based on your SaaS project if you have more than one with the same Paddle account.
9. Create a pricing page
Add the following template tag either on the start page or on a separate pricing page view:
{% load paddle_subscriptions_tags %}
{% paddle_subscriptions_pricing %}
10. Copy the templates to your project
Copy the templates from paddle_subscriptions/templates
in site-packages to your project and adjust them as necessary.
The copy of the templates serves two purposes: you can collect translatable strings into your project, and you can make the TailwindCSS classes discoverable by your installation.
11. Update your signup and account deletion views
Your Signup view must link to the subscription that has been done before signup or create a new subscriber with a free plan if they haven't subscribed yet:
from paddle_subscriptions.services import (
get_validated_unlinked_transaction,
connect_new_user_to_subscription,
create_subscriber_for_free_plan,
get_initial_data_for_new_user,
)
@transaction.atomic
@never_cache
def register(request, *arguments, **keywords):
"""
Displays the registration form and handles the registration action
"""
m = hashlib.md5()
m.update(force_bytes(request.META["REMOTE_ADDR"]))
request.session.session_id = m.hexdigest()[:20]
transaction = None
if transaction_id := request.COOKIES.get("paddle_transaction_id"):
transaction = get_validated_unlinked_transaction(transaction_id)
if request.method == "POST":
form = RegistrationForm(request, request.POST)
if form.is_valid():
user = form.save()
if transaction:
connect_new_user_to_subscription(user, transaction, subscriber_name=form.cleaned_data["company_name"])
else:
create_subscriber_for_free_plan(user, subscriber_name=form.cleaned_data["company_name"])
response = redirect("accounts:register_pending")
if transaction:
response.delete_cookie("paddle_transaction_id")
return response
else:
initial = None
if transaction:
initial = get_initial_data_for_new_user(transaction)
initial["company_name"] = transaction.business.name if transaction.business else ""
form = RegistrationForm(request, initial=initial)
if transaction:
form.fields["email"].widget.attrs["readonly"] = True
request.session.set_test_cookie()
response = render(
request,
"accounts/signup.html",
{"form": form},
)
return response
Then your account deletion view must cancel the existing subscription:
from paddle_subscriptions.services import cancel_subscription
@login_required
def delete_account(request):
context = {}
if (
request.user.is_authenticated
and not request.user.is_staff
and request.method == "POST"
):
form = DeleteAccountForm(user=request.user, data=request.POST)
if form.is_valid():
user = request.user
if subscription := request.subscriber.current_subscription:
cancel_subscription(subscription, effective_from="immediately")
if request.subscriber.membership_set.count() == 1:
request.subscriber.delete()
auth_logout(request)
form.delete()
response = redirect("accounts:delete_account_complete")
return response
else:
form = DeleteAccountForm(user=request.user)
context["form"] = form
return render(request, "accounts/delete_account.html", context)
How to Use
1. The subscription details page
The page at https://example.com/subscriptions/
allows you to subscribe to a paid plan or view the details of your current subscription.
Use the {% url "paddle_subscriptions:subscription_details" %}
in the templates to link to that page.
Test the pausing, resuming, and cancelling subscriptions there. Also test the billing history and invoices.
2. Check subscriber status in views or templates
These are some common values that might be interesting in your views and templates about the current subscriber:
# The slug of the current subscriber's subscription plan
request.subscriber.plan.slug
# Does the current subscriber have free access to their subscription plan?
# (the plan itself is free or the subscriber has an exclusive manually set free access)
request.subscriber.has_free_access
# Is the current subscription active?
request.subscriber.is_subscription_active
# Will the current subscription be paused at the end of the billing cycle?
request.subscriber.is_subscription_to_be_paused
# Is the current subscription paused at the moment?
request.subscriber.is_subscription_paused
# Will the current subscription be cancelled at the end of the billing cycle?
request.subscriber.is_subscription_to_be_cancelled
# Is the current subscription cancelled at the moment?
# (only possible for pricing without a free plan)
request.subscriber.is_subscription_cancelled
3. Translate the strings in your templates
If your website has more than one language, prepare the translations:
- Use management command
makemessages
to collect translatable strings intodjango.po
files. - Translate the strings to the languages you need.
- Then, use the management command
compilemessages
to compile them todjango.mo
files.
When going live
Paddle has an extensive list of steps about going live. Read it thoroughly. What relates to Django Paddle Subscriptions follows:
1. Set Paddle environment to live
Set the environment to "live":
PADDLE_SUBSCRIPTIONS["ENVIRONMENT"] = "live"
Make sure that PADDLE_SUBSCRIPTIONS["WEBSITE_URL"]
points to the URL of the production website, which has to be approved by Paddle.
Remove the PADDLE_SUBSCRIPTIONS["RESTRICTED_TO_IPS"]
setting.
2. Flush staging data and set up Paddle subscription anew
(venv)$ python manage.py flush_paddle_billing_models
(venv)$ python manage.py set_up_paddle_subscriptions
Then, link your subscription plans with live monthly and yearly prices.
3. Test live payments and subscriptions
Create a 100% discount code on the Paddle live environment and test your subscriptions with that code.