If you are looking to customize the default theme, please check the default theme wireframe instead.

Starting with a pricing page

templates/saas/pricing.html
displays the plans available for subscription.

The homepage and other static landing pages can be hosted on DjaoDjin, but there is nothing special about them.

The first page interacting with the djaoapp proxy that we will customize is the pricing page.

First we wrap the HTML displaying a single plan into a for loop, replacing hardcoded plan title, price amount and description with their actual value taken form the database.

saas/pricing.html
+{% for plan in plan_list %}
+<div>
+  <h2>{{plan.title}}</h2>
+  <h2>{{plan.period_amount}}</h2>
+  <p>{{plan.description}}</p>
+</div>
+{% endfor %}

With a few plans created for our site, we see they show up on the pricing page. The prices are displayed in cents and the description are not HTML formatted. In order to fix that, we add some filters.

saas/pricing.html
+{% for plan in plan_list %}
 <div>
   <h2>{{plan.title}}</h2>
+  <h2>{{plan.period_amount|htmlize_money}}</h2>
+  <p>{{plan.description|md}}</p>
 </div>
 {% endfor %}

It looks good now! It is time to add a subscribe button so users can subscribe to a plan.

saas/pricing.html
+<form method="post" action=".">
+  <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
 {% for plan in plan_list %}
 <div>
   <h2>{{plan.title}}</h2>
   <h2>{{plan.period_amount}}</h2>
   <p>{{plan.description}}</p>
+  <button type="submit" name="submit" value="{{plan.slug}}">Subscribe</button>
 </div>
 {% endfor %}
+</form>

Registering the first user

Automatically when visitors click subscribe on a plan, they are redirected to the registration page (see Workflows: Checkout). We will thus create a template page, i.e. accounts/register.html.

Registering a user is a simple form asking for at least an email and password. A few other optional information can also be asked on registration.

First, as with all other form pages, we make sure the form declares method="post" and action=".", as well as passing the authenticity token back to the server by adding <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">.

Many times, registration and login pages are inserted into a page flow, moving from a public page to a page that requires an authenticated user. It would be very annoying to a user that provides credentials to be redirected to a standard landing page instead of the page she intended to reach when she clicked on a link on the public page. The page context always contains the intended redirect url as variable next. We just need to append it to the action.

accounts/register.html
+<form method="post" action=".{% if next %}/?next={{next}}{% endif %}">
+  <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
+  <input class="btn btn-primary" name="commit" type="submit" value="Sign up">
+</form>

Inside the form, we create input fields for email, first name, last name, password and password confirmation.

accounts/register.html
 <form method="post" action=".">
  <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
+ <input name="email" type="email">
+ <input name="first_name" type="text">
+ <input name="last_name" type="text">
+ <input name="password" type="password">
+ <input name="password2" type="password">
  <input class="btn btn-primary" name="commit" type="submit" value="Sign up">
 </form>

When we try this form, we quickly realize that when something wrong happens on submit, we get back a blank form. No error message is displayed. This is a little inconvienient. We will add some HTML at the top of the <body> to display error messages back to the user.

accounts/register.html
+<div class="messages">
+{% for message in messages %}
+  <p class="{{message.tags}}">{{message}}</p>
+{% endfor %}
+</div>

We also add code to display field specific errors. For example, here is how errors related to the e-mail field are displayed back to the user.

accounts/register.html
 <form method="post" action=".">
 <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
 <input name="email" type="email">
+{% for error in form.email.errors %}
+  <span>{{error}}</span>
+{% endfor %}

At this point, errors when the form is submitted are displayed back to the user. The form fields are blank though. We would like users to avoid re-typing correct information they already entered. In order to do that we add the following code:

accounts/register.html
...
 <form method="post" action=".">
 <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
-  <input name="email" type="email">
+  <input name="email" type="email" {{form.email|value_attr}}>
   {% for error in form.email.errors %}
   <span>{{error}}</span>
   {% endfor %}

Going forward, all forms can be built on the same model.

Email notifications

To communicate with the user through e-mail, the system sends notifications. Email templates are structured in 3 blocks:

  • subject
  • html_content
  • plain_content (optional)

The subject block contains the subject line of the e-mail. The html_content block contains the content of the e-mail, HTML formatted. The plain_content block contains the content of the e-mail in plain text. In case there are no plain_content block, the system will create it as a stripped down version of the html_content block.

The first e-mail notification we will customize is the password reset e-mail.

notification/password_reset.eml (example)
Email sent to a user with a clickable link to reset her password.

Here in its bare bone version with a subject and html_content blocks.

notification/user_reset_password.eml
{% block subject %}
Reset your password on example.com

{% block html_content %}
Please reset your password on example.com by visiting the following
url:

{{back_url}}

Link is valid for {{expiration_days}} days.
{% endblock %}

Here the context contains back_url, the URL generated by the system to reset a user password. The expiration_days contains the number of days the link is valid.

Checkout pipeline pages

saas/billing/cart.html
saas/billing/cart-periods.html
saas/billing/cart-seats.html
Pages to enter a credit card, number of pre-paid periods, etc.

Once a user can register and do the basic login/logout operations and our pricing page is up and running, we move on to the checkout pipeline. The checkout pipeline can be as simple as a form to enter a credit card number or as complex as a multi-page experience with multi-periods at a discount and group billing.

Customizing the saas/billing/cart.html template consists of adding a form with purposefully named input fields, and including a saas/_payment_head.html and saas/_payment_body.html boiler plate in the <head> and <body> nodes respectively.

The minimum form should look like:

saas/billing/cart.html
<form id="payment-form" method="post" action=".{% if next %}/?next={{next}}{% endif %}">
    <input type="hidden" name="csrfmiddlewaretoken" value="{{csrf_token}}">
    <div id="card-use">
        <!-- BE CAREFULL: Do not add name="" to the #card-number,
             #card-cvc, #card-exp-month, #card-exp-year input nodes,
             else values will hit the server and break PCI compliance. -->
        <div>
            <input id="card-number" type="text" autocomplete="off" />
        </div>
        <div>
            <input id="card-cvc" type="text" autocomplete="off" />
        </div>
        <div>
            <input id="card-exp-month" type="text" autocomplete="off" />
        </div>
        <div>
            <input id="card-exp-year" type="text" autocomplete="off" />
        </div>
        <div>
            <input name="card_name" type="text" />
        </div>
        <div>
            <input name="card_address_line1" type="text" />
        </div>
        <div>
            <input name="card_city" type="text" />
        </div>
        <div>
            <input name="region" type="text" />
        </div>
        <div>
            <input name="card_address_zip" type="text" />
        </div>
        <div>
            <input name="country" type="text" />
        </div>
    </div>
</form>

We then add the following boiler plate code in the <head> node:

saas/billing/cart.html
<script src="//code.jquery.com/jquery-1.11.1.min.js" type="text/javascript"></script>
<meta name="csrf-token" content="{{csrf_token}}">
{% include "saas/_payment_head.html" %}

We add the following boiler plate code in the <body> node:

saas/billing/cart.html
{% include "saas/_payment_body.html" %}

Of course, to display error messages, we follow a similar pattern as seen earlier in: Registering the first user.

For simplicity and consistency, both saas/billing/cart-periods.html and saas/billing/cart-seats.html can be directly derived from the saas/billing/cart.html. All three templates share the same context.

Charge Receipt

saas/billing/receipt.html
HTML page displayed once the charge is confirmed.
saas/printable_charge_receipt.html
PDF printable version of the charge confirmation.
notification/charge_receipt.eml
Email sent to the user to confirm the charge.

Once the checkout pipeline is completed, a charge on Stripe and receipt are generated. Typically we want to add a link on the charge receipt so that a user can directly browse to the next part of the site that makes sense in the on-boarding.