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 not much has been written about how to do it 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 (the credit card number is valid, the expiration date is still in the future, etc.), 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 developers that allows integration with the Braintree API. It is easily installed 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 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 (e.g., because the card’s expiration date has passed), the submission process will not be allowed to continue. Post-submission validation errors can be accessed by passing a callback function 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.)

(There are other options that can be passed to the “hosted fields” dictionary to handle styling and pre-submission validation. Again, there is a link to Braintree’s documentation 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 "forms.py" - specifically into the “clean” method of the form that the user uses to make the purchase.

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.

(forms.py)

from django import forms
...
class CreditCardForm(forms.Form):
    payment_method_nonce = self.cleaned_data['payment_method_nonce']

    def clean(self):
        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.                                                                
            self.cleaned_data['braintree_transaction_id'] = result.transaction.id
        else:
            errors = ", ".join([e.message for e in result.errors.deep_errors])
            raise forms.ValidationError(errors)

It is appropriate that we perform this operation in the “clean” method because at this point something could still go wrong. There could be network / server difficulties, or the transaction could be denied for insufficient funds. We can check the status of the sale by checking the true/false value of the result object’s “is_success” member. If this is true, a unique transaction id can be saved into the form’s “cleaned_data” dictionary:

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. 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 (compare code snippet above). Other details of transactions are available in Braintree’s documentation (see links below).

If the user submits invalid data in the hosted fields, details of the resulting validation errors (which come from Braintree) can be viewed by passing an “onError” handler as yet another option to the “setup()” method. As before, details are available from Braintree’s online documentation (see links below).

If the value of “is_success” is False due to errors other than validation errors, these errors will be reported in “result.errors.deep_errors” (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.