Integrating Chargebee payments with Django

(Comments)

What is Chargebee?


Chargebee is a SaaS billing and subscription system that will make your life a lot easier if your service has payment needs. It provides all the basic features to manage billing, subscriptions, invoicing, recurring payments, advance billing and a lot more. And it has a great API - with libraries provided in all major programming languages - that makes its integration into our apps seamless.


Signup for chargebee at https://www.chargebee.com/trial-signup.html. Chargebee provides you with a test site, a virtual sandbox, where you can test and experiment with the payments. Once you are sure that it works as per your requirement, we can start our live site.


Complete the account setup following the link that will be mailed to you. Once the setup is done you will be taken to the dashboard of the test site. Your site will already have a lot of sample data.

Setup account


Installation and Django Setup


The chargebee's python client library can be installed using pip with

pip install 'chargebee>=2,<3'

Or, if you are using easy_install

easy_install --upgrade 'chargebee>=2,<3'

Obtain the API key from settings->API & WEBHOOKS -> Your API Keys.

Add the following settings to your django settings file

CHARGEBEE_APIKEY = 'your_apikey'
CHARGEBEE_SITENAME = 'your_chargebee_site_name'

And remember not to share your keys with anyone and not to include them in version control systems (git, mercurial etc.).

Now we are ready to work with chargebee in our django application.


Create a module named chargebee_utils.py and initialise chargebee.

import chargebee
from django.conf import settings

chargebee.configure(settings.CHARGEBEE_APIKEY, settings.CHARGEBEE_SITENAME)

This will initiate chargbee.


Creating the payment page


Before you take a user to the payment page and start charging them, you need to create a plan. To simply put, a plan determines the charges and billing frequency of a subscription. You can create a plan from product catalog -> plans -> Create A New Plan. Setup the price for the plan, setup cost if required and billing period.


Now let us look at the requirements.

  1. When a user signs up to your application he/she should be redirected to the payment page (we will use only a single plan to keep things simple)
  2. Once the payment is done he must be redirected back to the application. If the payment fails or it is not completed user should be redirected to the payment page to complete the payment.
  3. When user logs in to the application, his/her subscription status must be checked.
  4. User must be disallowed to use a particular view from using, if he/she is not subscribed to the plan.

Let us implement these in the django application.


1. When a user signs up to your application he/she should be redirected to the payment page.

Create a model to store the mapping of the django application user with the customer created in chargebee.

from django.db import models
from django.conf import settings


def ChargebeeData(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    customer_id =  models.CharField(max_length=40) # Chargebee customer id
    page_id = models.CharField(max_length=40, blank=True, null=True)

Add the following methods to chargebee_utils.py.

import chargebee
from chargebee import InvalidRequestError


def create_customer(data):
    if 'id' in data:
        del data['id']
        # Let chargebee handle creation of customer id

    try:
        result = chargebee.Customer.create(data)
        return result.customer
    except InvalidRequestError as e:
        return None


def create_checkout(customer, plan_id):
    plan_data = {
            'plan_id': plan_id
        }
    checkout_data = ({
        'subscription': plan_data,
        'customer': {
            'id': customer.id
        }
    })

    try:
        checkout = chargebee.HostedPage.checkout_new(checkout_data)
        if checkout.hosted_page.state == 'created':
            return checkout.hosted_page
    except InvalidRequestError:
        return None

In the signup view, create a chargebee customer as soon as new user is saved in the database.

from django.db import transaction, DatabaseError
from django.shortcuts import redirect
import chargebee_utils as cb
from .models import ChargebeeData


def signup_view(request):
    # Some logic that gives us signup form
    if signup_form.is_valid():
        try:
            with transaction.atomic():
                # Using a transaction we can ensure the consistency between our application users
                # and the chargebee customers
                user = signup_form.save()

                customer = cb.create_customer(form.cleaned_data)
                if customer is None:
                    raise DatabaseError
                sub_data = ChargebeeData.create(user=user, customer_id=customer.id)
                page = cb.create_checkout(customer, 'premium') # 'premium' is the plan id
                sub_data.page_id = page.id
                sub_data.save()
                return redirect(page.url)
        except DatabaseError as e:
            # Handle the error appropriately
            # User object is not saved to the database at this point

Note that we are hard coding the plan id premium in the code which might not satisfy your needs. A more plausible approach would be to let the user select a plan from multiple options based on the features of your service the user needs.

2. Once the payment is done he/she must be redirected back to the application.


This step is simple. Create a new view in your django application to handle redirection. Let us say that the views are served at the url https://www.youdomain.com/payment-confirm. Now go to settings -> HOSTED PAGES SETTINGS -> Checkout Page and set the fields "Redirect URL" and "Cancel URL" to "https://www.youdomain.com/payment-confirm" (replace it with your actual URL). Now when a payment is made successfully or is cancelled, the chargebee's checkout page is redirected to this URL.


First update the chargebee_utils.py module with the following method.

def get_hosted_page(id):
    try:
        plan = chargebee.HostedPage.retrieve(id)
        return plan.hosted_page
    except Exception as e:
        return None

Write your view the handle the callback as follows.

def confirm_payment(request):
    # The page id is passed in as url parameter 'id'
    page = cb.get_hosted_page(request.GET.get('id'))
    if page is None:
        # The page id is invalid handle it appropriately

    elif page.state == 'succeeded':
        # Payment is done. Hadle appropriately

    else # page.state == 'cancelled':
        # Payment has been cancelled. Handle appropriately

In case the payment is cancelled, the page url is not valid any more (cannot be used again to complete payment). A new checkout page has to be created (remember we did that during the signup) to complete payment. So handle the case appropriately.


3. When user logs in to the application, his/her subscription status must be checked.


Update the chargbee_utils.py module with the following method that checks if a customer is subscribed to a plan.

def is_user_subscribed(user):
    if user is None:
        return False

    subscription = ChargebeeData.objects.get(user=user)
    subscriptions = chargebee.Subscription.list({
        'limit': 1,
        'plan_id': 'premium', # Replace with you own plan id
        'customer_id[is]': subscription.customer_id,
        'status[in]': "[in_trial, active, non_renewing]"
    })
    return len(subscriptions) > 0

Then we need to customize the django Authentication Backend (see customising authentication). An authentication backend in just a python class that implements def authenticate() method. Create a module named authentication.py and define a class in it.

import chargebee_utils as cb

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User


class CustomBackend(ModelBackend):
    def authenticate(self, *args, **kwargs):
        user = super(CustomBackend, self).authenticate(*args, **kwargs)
        request.session['subscription_active'] = cb.is_user_subscribed(user)
        return user

Set this class as the authentication backend in django settings module.

AUTHENTICATION_BACKENDS = ['myapp.authentication.CustomBackend']

4. User must be disallowed to use a particular view from using, if he/she is not subscribed to the plan.

This is easy to achieve by defining a decorator on our django views. Lets define a method in decorators.py module named subscribe_to() as follows.

from django.http import HttpResponseForbidden

def subscription_required(view):
    def _wrapped_view(request, *args, **kwargs):
        if request.user.is_authenticated():
            if not request.session.get('subscription_active', True):
                return HttpResponseForbidden('Unauthorized')

        return view(request, *args, **kwargs)

    return _wrapped_view

Now use this decorator over your views as follows.

from .decorators import subscription_required

@subscription_required
def my_restricted_view(request):
     # Your view method
     pass

You can use a parameterized decorator to use it with different plans if needed. That is how chargebee provides all the functionality to manage our payment needs. For further information visit the official api documentation of chargebee.

Comments

Recent Posts

Archive

2021
2020
2019
2018
2017
2016
2015
2014

Tags

Authors

Feeds

RSS / Atom