Blog | CoWhite Softwarehttp://django.cowhite.com/blog/2017-07-04T14:34:15+00:00BlogIntegrating Chargebee payments with Django2017-07-04T14:34:15+00:00ravi/blog/author/ravi/http://django.cowhite.com/blog/integrating-chargebee-with-django/<p><strong>What is Chargebee?</strong></p>
<p><br></p>
<p><a href="https://www.chargebee.com/" target="_blank">Chargebee</a> 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.</p>
<p><br></p>
<p>Signup for chargebee at <a href="https://www.chargebee.com/trial-signup.html" target="_blank">https://www.chargebee.com/trial-signup.html</a>. 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.</p>
<p><br></p>
<p>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. </p>
<p><img alt="Setup account" src="http://django.cowhite.com/static/media/uploads/Chargebee%20posts/rsz_screenshot_from_2017-07-04_11-24-08-min.png" /></p>
<p><br></p>
<p><strong>Installation and Django Setup</strong></p>
<p><br></p>
<p>The chargebee's python client library can be installed using <code>pip</code> with</p>
<div class="codehilite"><pre>pip install 'chargebee>=2,<3'
</pre></div>
<p>Or, if you are using <code>easy_install</code></p>
<div class="codehilite"><pre>easy_install --upgrade 'chargebee>=2,<3'
</pre></div>
<p>Obtain the API key from <code>settings->API & WEBHOOKS -> Your API Keys</code>.</p>
<p>Add the following settings to your django settings file</p>
<div class="codehilite"><pre>CHARGEBEE_APIKEY = 'your_apikey'
CHARGEBEE_SITENAME = 'your_chargebee_site_name'
</pre></div>
<p>And remember not to share your keys with anyone and not to include them in version control systems (git, mercurial etc.).</p>
<p>Now we are ready to work with chargebee in our django application.</p>
<p><br></p>
<p>Create a module named <code>chargebee_utils.py</code> and initialise chargebee.</p>
<div class="codehilite"><pre><span class="kn">import</span> <span class="nn">chargebee</span>
<span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">settings</span>
<span class="n">chargebee</span><span class="o">.</span><span class="n">configure</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">CHARGEBEE_APIKEY</span><span class="p">,</span> <span class="n">settings</span><span class="o">.</span><span class="n">CHARGEBEE_SITENAME</span><span class="p">)</span>
</pre></div>
<p>This will initiate chargbee. </p>
<p><br></p>
<p><strong>Creating the payment page</strong></p>
<p><br></p>
<p>Before you take a user to the payment page and start charging them, you need to create a plan. To simply put, a <code>plan</code> determines the charges and billing frequency of a subscription. You can create a plan from <code>product catalog -> plans -> Create A New Plan</code>. Setup the price for the plan, setup cost if required and billing period. </p>
<p><br></p>
<p>Now let us look at the requirements.</p>
<ol>
<li>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)</li>
<li>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.</li>
<li>When user logs in to the application, his/her subscription status must be checked.</li>
<li>User must be disallowed to use a particular view from using, if he/she is not subscribed to the plan.</li>
</ol>
<p>Let us implement these in the django application.</p>
<p><br></p>
<p><strong>1. When a user signs up to your application he/she should be redirected to the payment page.</strong></p>
<p>Create a model to store the mapping of the django application user with the customer created in chargebee.</p>
<div class="codehilite"><pre><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">settings</span>
<span class="k">def</span> <span class="nf">ChargebeeData</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">OneToOneField</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">AUTH_USER_MODEL</span><span class="p">)</span>
<span class="n">customer_id</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">40</span><span class="p">)</span> <span class="c"># Chargebee customer id</span>
<span class="n">page_id</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">40</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</pre></div>
<p>Add the following methods to <code>chargebee_utils.py</code>.</p>
<div class="codehilite"><pre><span class="kn">import</span> <span class="nn">chargebee</span>
<span class="kn">from</span> <span class="nn">chargebee</span> <span class="kn">import</span> <span class="n">InvalidRequestError</span>
<span class="k">def</span> <span class="nf">create_customer</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="k">if</span> <span class="s">'id'</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
<span class="k">del</span> <span class="n">data</span><span class="p">[</span><span class="s">'id'</span><span class="p">]</span>
<span class="c"># Let chargebee handle creation of customer id</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">chargebee</span><span class="o">.</span><span class="n">Customer</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">customer</span>
<span class="k">except</span> <span class="n">InvalidRequestError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="k">def</span> <span class="nf">create_checkout</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="n">plan_id</span><span class="p">):</span>
<span class="n">plan_data</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">'plan_id'</span><span class="p">:</span> <span class="n">plan_id</span>
<span class="p">}</span>
<span class="n">checkout_data</span> <span class="o">=</span> <span class="p">({</span>
<span class="s">'subscription'</span><span class="p">:</span> <span class="n">plan_data</span><span class="p">,</span>
<span class="s">'customer'</span><span class="p">:</span> <span class="p">{</span>
<span class="s">'id'</span><span class="p">:</span> <span class="n">customer</span><span class="o">.</span><span class="n">id</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">checkout</span> <span class="o">=</span> <span class="n">chargebee</span><span class="o">.</span><span class="n">HostedPage</span><span class="o">.</span><span class="n">checkout_new</span><span class="p">(</span><span class="n">checkout_data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">checkout</span><span class="o">.</span><span class="n">hosted_page</span><span class="o">.</span><span class="n">state</span> <span class="o">==</span> <span class="s">'created'</span><span class="p">:</span>
<span class="k">return</span> <span class="n">checkout</span><span class="o">.</span><span class="n">hosted_page</span>
<span class="k">except</span> <span class="n">InvalidRequestError</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">None</span>
</pre></div>
<p>In the signup view, create a chargebee customer as soon as new user is saved in the database.</p>
<div class="codehilite"><pre><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">transaction</span><span class="p">,</span> <span class="n">DatabaseError</span>
<span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">redirect</span>
<span class="kn">import</span> <span class="nn">chargebee_utils</span> <span class="kn">as</span> <span class="nn">cb</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">ChargebeeData</span>
<span class="k">def</span> <span class="nf">signup_view</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="c"># Some logic that gives us signup form</span>
<span class="k">if</span> <span class="n">signup_form</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="n">transaction</span><span class="o">.</span><span class="n">atomic</span><span class="p">():</span>
<span class="c"># Using a transaction we can ensure the consistency between our application users</span>
<span class="c"># and the chargebee customers</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">signup_form</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="n">customer</span> <span class="o">=</span> <span class="n">cb</span><span class="o">.</span><span class="n">create_customer</span><span class="p">(</span><span class="n">form</span><span class="o">.</span><span class="n">cleaned_data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">customer</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">DatabaseError</span>
<span class="n">sub_data</span> <span class="o">=</span> <span class="n">ChargebeeData</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">user</span><span class="o">=</span><span class="n">user</span><span class="p">,</span> <span class="n">customer_id</span><span class="o">=</span><span class="n">customer</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
<span class="n">page</span> <span class="o">=</span> <span class="n">cb</span><span class="o">.</span><span class="n">create_checkout</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="s">'premium'</span><span class="p">)</span> <span class="c"># 'premium' is the plan id</span>
<span class="n">sub_data</span><span class="o">.</span><span class="n">page_id</span> <span class="o">=</span> <span class="n">page</span><span class="o">.</span><span class="n">id</span>
<span class="n">sub_data</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">page</span><span class="o">.</span><span class="n">url</span><span class="p">)</span>
<span class="k">except</span> <span class="n">DatabaseError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="c"># Handle the error appropriately</span>
<span class="c"># User object is not saved to the database at this point</span>
</pre></div>
<p>Note that we are hard coding the plan id <code>premium</code> 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.</p>
<p><strong>2. Once the payment is done he/she must be redirected back to the application.</strong></p>
<p><br></p>
<p>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 <code>https://www.youdomain.com/payment-confirm</code>. Now go to <code>settings -> HOSTED PAGES SETTINGS -> Checkout Page</code> 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.</p>
<p><br></p>
<p>First update the <code>chargebee_utils.py</code> module with the following method.</p>
<div class="codehilite"><pre>def get_hosted_page(id):
try:
plan = chargebee.HostedPage.retrieve(id)
return plan.hosted_page
except Exception as e:
return None
</pre></div>
<p>Write your view the handle the callback as follows.</p>
<div class="codehilite"><pre>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
</pre></div>
<p>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.</p>
<p><br></p>
<p><strong>3. When user logs in to the application, his/her subscription status must be checked.</strong></p>
<p><br></p>
<p>Update the chargbee_utils.py module with the following method that checks if a customer is subscribed to a plan.</p>
<div class="codehilite"><pre>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
</pre></div>
<p>Then we need to customize the django Authentication Backend (see <a href="https://docs.djangoproject.com/en/1.11/topics/auth/customizing/">customising authentication</a>). An authentication backend in just a python class that implements <code>def authenticate()</code> method. Create a module named <code>authentication.py</code> and define a class in it.</p>
<div class="codehilite"><pre><span class="kn">import</span> <span class="nn">chargebee_utils</span> <span class="kn">as</span> <span class="nn">cb</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth.backends</span> <span class="kn">import</span> <span class="n">ModelBackend</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth.hashers</span> <span class="kn">import</span> <span class="n">check_password</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth.models</span> <span class="kn">import</span> <span class="n">User</span>
<span class="k">class</span> <span class="nc">CustomBackend</span><span class="p">(</span><span class="n">ModelBackend</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">authenticate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">user</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">CustomBackend</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">authenticate</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">request</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s">'subscription_active'</span><span class="p">]</span> <span class="o">=</span> <span class="n">cb</span><span class="o">.</span><span class="n">is_user_subscribed</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
<span class="k">return</span> <span class="n">user</span>
</pre></div>
<p>Set this class as the authentication backend in django settings module.</p>
<div class="codehilite"><pre>AUTHENTICATION_BACKENDS = ['myapp.authentication.CustomBackend']
</pre></div>
<p><strong>4. User must be disallowed to use a particular view from using, if he/she is not subscribed to the plan.</strong></p>
<p>This is easy to achieve by defining a decorator on our django views. Lets define a method in <code>decorators.py</code> module named <code>subscribe_to()</code> as follows.</p>
<div class="codehilite"><pre><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponseForbidden</span>
<span class="k">def</span> <span class="nf">subscription_required</span><span class="p">(</span><span class="n">view</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">_wrapped_view</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">is_authenticated</span><span class="p">():</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'subscription_active'</span><span class="p">,</span> <span class="bp">True</span><span class="p">):</span>
<span class="k">return</span> <span class="n">HttpResponseForbidden</span><span class="p">(</span><span class="s">'Unauthorized'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">view</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">_wrapped_view</span>
</pre></div>
<p>Now use this decorator over your views as follows.</p>
<div class="codehilite"><pre><span class="kn">from</span> <span class="nn">.decorators</span> <span class="kn">import</span> <span class="n">subscription_required</span>
<span class="nd">@subscription_required</span>
<span class="k">def</span> <span class="nf">my_restricted_view</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="c"># Your view method</span>
<span class="k">pass</span>
</pre></div>
<p>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 <a href="https://apidocs.chargebee.com/docs/api" target="_blank">official api documentation</a> of chargebee.</p>