Tech Blog

Braintree Integration with Django

Braintree Integration with Django

Recently updated on

Much has been written about how to implement online payment using the services available from Braintree, but little about how to do it specifically within a Django framework. Combining the two does not require any special contortions, but as Braintree’s own online documentation has been written without any particular framework in mind, figuring out which parts of the integration should go into which parts of a Django project can represent a modest barrier to entry. This article will cover all parts of the Braintree code used for credit card transactions in a simple “hosted fields” integration with Django (Braintree offers two additional methods of integration, but only hosted fields will be covered here). We will begin with a brief overview of a transaction and then address each step in greater detail.

Throughout this document the word server will refer to the computer that is running Django and serving your web pages. The document is written from the point of view of a developer, so references to “your server”, “your website”, etc. indicate server-side operations. The word client will refer to the user’s computer, where the user is the person visiting your website in a browser. The third player is Braintree itself. During a transaction, both the server and the client communicate with Braintree as well as with each other.

Overview

Braintree provides an excellent diagram that explains the stages of an online transaction, but here is our own brief overview.

If a user wants to use a credit card to buy something from your website, they will at some point have to enter their sensitive credit card information (number, expiration date, and three-digit CVV code) into a form. This is where Braintree’s hosted fields come in: although the form fields that accept non-sensitive information will be provided by your server, the fields that accept the sensitive information will be provided by Braintree and made visible on your website through an iframe. In this way, your server can avoid any and all contact with the user’s sensitive credit card information (hosted fields will be addressed in greater detail below). By so doing, you can enable credit card transactions on your website while maintaining PCI compliance.

But Braintree still has to know who you are so that the user’s payment can be transferred to you. Therefore, when the client (the user's computer) requests the payment form from your server, the server responds by sending the client a “client token” (along with the non-sensitive elements of the form). The client then forwards this token to Braintree, which responds by sending the client the information it needs to construct the form fields for sensitive information. This step is a little counterintuitive: The client communicates with Braintree when the form is initialized - before the user submits the form -, and this communication requires the client token. Once the form is initialized, the client token’s job is done.

Later, when the user submits the form, the values in the sensitive fields are sent to Braintree. (Note that the total payment amount is not sent to Braintree at this time!) Braintree then validates the values in the fields. If they all check out, Braintree responds by sending a “payment nonce” back to the client. The nonce is just a short string of characters that Braintree will recognize as authorization for a transaction. The client then forwards the nonce to your server (along with the non-sensitive information entered into the form). Your server-side code can then use the nonce to instruct Braintree to finalize the transaction. At no point has the user’s credit card information been sent to your server.

We will now address each of these steps in greater detail and with greater focus on integrating them into a Django project.

Server Side Part I

As we saw in the overview, using Braintree requires both server-side code (written in Python and executed on your server) and client-side code (written in Javascript and executed on the client). The Braintree server-side library, which you will need to generate the client token and to process the payment nonce, is a third-party Python module developed by Braintree that allows integration with the Braintree API. You can install it with:

(your_virtual_env)$ pip install braintree

After the library is installed, you must obtain your three API credentials: a merchant id, a public key, and a private key. These values are the starting point of your Braintree integration. All three values will change as you transition from a testing into a production environment, but all are obtainable from your Braintree account. Regardless of whether you are in testing or production, you should place these values into your settings.py:

(settings.py)

BRAINTREE_MERCHANT_ID = '<your_merchant_id>'
BRAINTREE_PUBLIC_KEY = '<your_public_key>'
BRAINTREE_PRIVATE_KEY = '<your_private_key>'

Afterward, the environment and credentials can be configured in the views.py of the app that needs to contact Braintree. The call to the braintree object can be placed at or near the top of views.py, outside of any class delcarations or function definitions:

(views.py)

import braintree
...
braintree.Configuration.configure(braintree.Environment.Sandbox,
    merchant_id=settings.BRAINTREE_MERCHANT_ID,
    public_key=settings.BRAINTREE_PUBLIC_KEY,
    private_key=settings.BRAINTREE_PRIVATE_KEY)

We can then use the imported braintree object to generate the client token, which the client will use to configure a connection to the Braintree API. We generate the token on the initial GET request and store it in the session object. The relevant snippet from views.py might look like this:

(views.py)

def payment_view(request):
    if request.method == 'GET':
        request.session['braintree_client_token'] = braintree.ClientToken.generate()
        …
        return render(request, ‘path/to/payment_template.html’)
    else:  # We assume this is a ‘POST’ request.
        if not form.is_valid():
            return render(render, ‘path/to/payment_template.html’)
        else:  # The transaction can be finalized.
            …
    ...

Client Side

For the moment, the server’s role is over. The client has received the client token that it needs to contact Braintree and initiate communication with their API. Execution thus passes to javascript code running on the client.

To contact Braintree, the client needs access to the Braintree Javascript library. There are a few ways to obtain this, but we did it by adding <script> tags to the Django template for the payment view:

(payment_template.html)

<script src="https://js.braintreegateway.com/js/braintree-2.20.0.js">
</script>

Afterward, the client must call braintree.setup(), which initializes the braintree object. The method should be called as soon as the page has finished being rendered (i.e., upon page initialization, not upon form submission). Here is what your template might look like:

(payment_template.html)

<form id=”creditcard_form” method=”post” action=”{% url ‘checkout_view’ %}”>
    <!-- input elements for non-sensitive user data -->
    <label for="id_number">Card number</label>
    <div id="id_number"></div>

    <label for="id_expiration_month">Expiration month</label>
    <div id="id_expiration_month"></div>

    <label for="id_expiration_year">Expiration year</label>
    <div id="id_expiration_year"></div>

    <label for="id_verification_code">CVV</label>
    <div id="id_verification_code"></div>
</form>

the rest of your template can go here. Put the call to setup() at the bottom

<script type="text/javascript">
braintree.setup(
    "{{ request.session.braintree_client_token }}",
    "custom",
    {
        id: "creditcard_form",
        hostedFields: {
            number: {
                selector: "#cc_number"
            },
            expirationDate: {
                selector: "#cc_expiration_date"
            },
            cvv: {
                selector: "#cc_verification_code"
            },
        },
    }
);
</script>

As we see in the snippet above, the setup method takes three parameters: the client token, the integration type (just a string, either “dropin” or “custom”), and a dictionary of options. We will now address each of these in order:

1. The client token Note that the template accesses the client token via the session object. Passing the token to setup() allows Braintree to supply the form fields for sensitive data so that the form can be rendered on the user’s computer.

2. The integration type Our integration uses Braintree’s “hosted fields” (more below), a method that requires that our second parameter be the string “custom”.

3. The options The options supply the id attribute of your form and also (in the inner dictionaries of the hostedFields option) the id attributes of some empty <div> elements within that form. Calling setup() causes these empty <div> elements to be replaced by hosted fields, which are simply <iframe> elements whose src attributes point to Braintree’s servers. This is how the hosted fields integration ensures that your server has no contact with sensitive user credit card information. More details are available in the Braintree documentation (see below for a link).

Calling setup() has an additional function. It also causes a special event handler to be attached to the form’s submit button. This handler hijacks the normal submit method so that the hosted fields can be validated on the Braintree servers before anything else happens. If Braintree determines that the credit card data in the hosted fields is valid, the payment nonce will be inserted into the form inside a hidden input element with the name payment_method_nonce. The value of this input element will be a short string representing an authorization to charge the user’s credit card. The hidden element, along with the rest of the form, will then be allowed to proceed as normal through the regular Django validation process.

On the other hand, if the credit card information is deemed invalid, the submission process will not be allowed to continue. Validation errors on the sensitive, Braintree-hosted fields can be accessed in a callback function passed to the onError option of braintree.setup(). Braintree’s documentation has details on all the options that can be passed to braintree.setup() (see below.)

Server Side Part II

Assuming the form has validated, the information entered by the user - along with the payment method nonce from Braintree - will be sent back to your server for further processing. At this point Braintree has validated the user’s credit card information and sent an authorization in the form of a payment nonce back to your server. Your server may now create a transaction using the payment nonce and a dollar amount. For Django, we put this machinery into views.py after confirming that the submitted form's is_valid() method (which triggers validation of the form's non-sensitive fields) returns True.

Assuming your form is a subclass of django.forms.Form, The payment method nonce (which was placed by the Braintree submit handler inside a hidden input field on the client side) will be available in the form’s cleaned_data dictionary. The transaction can then be finalized by calling braintree.Transaction.sale(). Note that here is where we finally send the total payment amount to Braintree.

(views.py)

import braintree
...
def your_billing_view(request):
    ...
    payment_method_nonce = your_billing_form.cleaned_data['payment_method_nonce']

    result = braintree.Transaction.sale({
        "amount": grand_total_amount,
        "payment_method_nonce": payment_method_nonce,
        "options": {
            "submit_for_settlement": False
        }
    })
    if result.is_success:
        # Don't charge the customer yet - instead get the transaction                                                
        # id and use that later to complete the sale.                                                                
        braintree_transaction_id = result.transaction.id
    else:
        # Handle the error; details will be in "result.message"
        # and/or in "result.errors.deep_errors"

If the value of the result object’s is_success property is true, a transaction with a unique id will also be attached to the result object.

Note that in our integration, we wanted to introduce a delay between obtaining final authorization for the payment and actually withdrawing money from the user’s account (This is the purpose of setting submit_for_settlement to False in the options dictionary). The settlement can then be made later. However, the sale can also be made to complete immediately by calling the sale() method with the option submit_for_settlement set to True. Other details of transactions are available in Braintree’s documentation (see links below).

Conclusion

We have now walked through a basic integration of Braintree’s hosted fields into a Django project. Of course, there are many options available to the user that we have not addressed at all, but here are a few links to some of the options mentioned above. Happy coding!

Comments

Comments are closed.