Payment Processing with Braintree

You’ve built the web application of the century, and the users have rightly flooded to it. Cat pictures for everyone!

But alas, while your users indulge in cat-induced bliss, the cold hard reality of server costs cannot help but harsh your mellow. What is to be done?

Maybe, you could get the users to… pay for access to your incredible web application in all its multivarious splendour?

Braintree is a payment processor (now a subsidiary of PayPal), which boasts of a “simple, robust way to accept payments”, and with features like a drop-in payment ui and libraries for various programming languages enabling fairly easy integration, is a solid choice for accepting payments via credit card or PayPal.

While Braintree’s developer documentation is blessedly detailed, it’s possessed of a potentially confusing bevy of options, and its various implementation examples are spread out amongst a number of pages and platforms. So today, rather than reiterate any particular section of the docs, we’re going to take a look at an end-to-end example of a specific, straightforward scenario – accepting and processing a one-time, immediately settled payment in a web application.

Our languages of choice for today will be Python for the backend and, of course, JavaScript for the frontend, with the additional assumption of jQuery for the ease of $.ajax. The general logic for the backend will be portable to the other languages for which Braintree has released an SDK, e.g. Ruby, PHP, etc., and the frontend logic could certainly be implemented without jQuery.

We’re also going to assume the existence of a database for storing and retrieving item and transaction details. We’re also going to assume the use of Django as our web framework in the following examples, but the general concepts are portable to other frameworks.

 

Not Production Ready

One more important note before we get started – the code below should not be considered production ready. It is example-quality, meant for instructional purposes only! While it should go without saying that copy-pasting code is always a bad idea, copy-pasting example code for business critical purposes like payment processing is a big enough no-no to warrant an extra paragraph warning everyone against it. This is that paragraph. Copy-pasting == bad. Using this tutorial to learn == good.

With that established, let’s get started.

Server Setup and Token Generation

After signing up for a sandbox account and doing some basic setup in the braintree control panel, our first step is to install the Braintree Python Library, manually or via pip.

That done, we’ll want to store our configuration details somewhere. In Django we might perform our configuration in settings.py:

[code lang=”python”]
import os
import braintree

braintree.Configuration.configure(
os.environ.get(‘BT_ENVIRONMENT’, braintree.Environment.Sandbox),
os.environ.get(‘BT_MERCHANT_ID’, ‘your_sandbox_merchant_id’),
os.environ.get(‘BT_PUBLIC_KEY’, ‘your_sandbox_public_key’),
os.environ.get(‘BT_PRIVATE_KEY’, ‘your_sandbox_private_key’)
)
[/code]

Next, we need to setup an endpoint for the client to request a client token from:

[code lang=”python”]
import braintree
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods

@require_http_methods([‘GET’])
def get_braintree_client_token(request):
"""
Generate and return client token.
"""
try:
client_token = braintree.ClientToken.generate()
except ValueError as e:
return JsonResponse({"error": e.message}, status=500)
return JsonResponse({"token": client_token})
[/code]

For Django, we can add this to a reasonable route in our urls.py file, and then make ajax requests against this route to generate and return the client token that the javascript braintree library requires. For the purpose of this tutorial, we’ll assume we’ve bound it to /payment/token/.

As an aside, we could also generate this token as part of the view and pass it into the context if we knew we were going to need it immediately, or if there were some other reason why an ajax roundtrip is undesirable. This could be as simple as:

[code lang=”python”]
import braintree
from django.shortcuts import render
from django.http import HttpResponse

def start_payment_view(request, template_name="start_payment.html"):
"""
Generate client token and pass it in the view context.
"""
try:
client_token = braintree.ClientToken.generate()
except ValueError as e:
return HttpResponse("Failed to generate Braintree client token", status=500)

return render(request, template_name, {"bt_client_token": client_token})
[/code]

Client Setup

Now for client side configuration. On the client side, we’re going to assume we want to use the ‘Drop-in’ integration. This generates an iframe hosted by Braintree, allowing our application to qualify for the lowest-effort level of PCI Compliance, while still allowing for a fair amount of customizability, some of which we’ll explore shortly.

Braintree offers a variety of options for integrating the library with your application. For the below, we assume we have a braintree object in global scope:

[code lang=”javascript”]
/**
* Request client token and initialize braintree payment library.
*/
setupBraintree:function(){
$.ajax({
type:’GET’,
dataType:’json’,
url:’/payment/token/’
}).done(function(res){
braintree.setup(res.token, "dropin", {
container:$(‘#braintreeContainer’)[0],
onReady:paymentMethodReady,
onPaymentMethodReceived:paymentMethodReceived,
onError:paymentMethodError
});
}.bind(this)).fail(function(jqXHR){
console.log(jqXHR, jqXHR.responseJSON || jqXHR.responseText);
});
}
[/code]

Here, we request the client token and, as soon as we’ve received it from the server, initialize the braintree library, telling it where to place the ‘dropin’ iframe, and specifying a number of other options, which we’ll return to shortly.

As we need a node for the braintree iframe to live in, which you’ll notice in the above code we’ve given the id of braintreeContainer, a sparse example of the markup might look like:

[code lang=”html”]
<body>

<form>

<div id="braintreeContainer"></div>

<button type="submit">Pay Now</button>
</form>

</body>
[/code]

Notice the ‘Pay Now’ button – we’ll need to use it to trigger submission of the containing form (which in turn will trigger submission/processing of the input within the braintree iframe). Notice also that we don’t specify any form action – we’ll be submitting our payment details by ajax, but we could instead specify an action, and send needed details via hidden fields in the form.

At this point, if those additional options in the braintree.setup pointed to real functions, we would see something like:
braintree client example

Client Options

Let’s take a closer look at those setup options, then. Besides the obvious container option, we have three more: onReady, onPaymentMethodReceived, and onError, which we supply with callback functions. The Braintree documentation adequately explains each of them (and a number of others we aren’t taking a look at today, such as an option to enable CORS requests), so let’s instead take at what other things might do in the specified functions.

[code lang=”javascript”]
/**
* As this function informs us that the braintree setup is complete, if we were hiding the braintree container node
* or displaying some other dialog while it loaded, we could now safely reveal it to the user.
* @param {object} integration An object containing the teardown, paypal and deviceData objects, as noted
* in the braintree docs.
*/
paymentMethodReady:function(integration){
// e.g.
$(‘#braintreeContainer’).show();
}
/**
* Called by the braintree js SDK once the payment method has been received – that is, once the user has selected
* their paypal account or entered their credit card details.
* @param {object} recv Contains the following:
* nonce: The payment method nonce.
* type: A string representing the type of payment method generated; either ‘CreditCard’ or ‘PayPalAccount’.
* details: Additional details. See https://developers.braintreepayments.com/reference/client-reference/javascript/v2/configuration#onpaymentmethodreceived-details-object
*/
paymentMethodReceived:function(recv){
// We could be submitting information to our server via the form, and add information we need submitted as hidden
// fields on the form, but we can also submit the information we want to the server within this callback via ajax.
var details = {otherDetails:{/* object containing any other info we want to send up */, payment:recv};

$.ajax({
url:’/payment/process/’,
type:’POST’,
dataType:’json’,
contentType:’application/json’,
data:JSON.stringify(details)
}).done(function(res){
// Do something with the response from your server.
}.bind(this)).fail(function(jqXHR){
var res = jqXHR.responseJSON || jqXHR.responseText;
console.log(res);
}
}

/**
* On payment method error, this callback will receive a detail object, as noted in the setup options
* documentation, which gives us some idea as to what error occurred, allowing us to choose how to
* respond to it or inform the client about it.
* @param {object} detail
*/
paymentMethodError:function(detail){
console.log(detail);

if (detail.type == "VALIDATION"){
// Potentially display a notification to your user that validation failed.
return;
}

// Potentially display or log that another type of error occurred.
}
[/code]

Perform Transaction

You’ll notice that we specified an endpoint of /payment/process/ in the paymentMethodReceived callback on the client-side. So, on the server side, let’s define this endpoint and finally process our payment.

[code lang=”python”]
import braintree
import json

from django.views.decorators.http import require_http_methods
from django.http import JsonResponse

#from yourapp.models import SaleItem, TransactionRecord

@require_http_methods([‘POST’])
def perform_braintree_simple_sale(request):
"""
Perform one-time, immediate settlement sale transaction using
the payment nonce sent to us by the client.
On failure, return the failure details to the client.
"""
try:
details = json.loads(request.body)
except ValueError:
# Handle the error case where you fail to parse the request body
return JsonResponse(status=400)

payment_details = details[‘payment’]

# Note that we get the value of the transaction from a database row – generally speaking,
# it’s a bad idea to trust the client to define the transaction amount – otherwise, the client
# may alter the request to say, have themselves charged $1 for something you want to charge $100
# for, and if you don’t validate this, you’re out $99. Allowing the client to specify what ‘item’
# they’d like to purchase is safer, however, as long as the server remains in control of setting and
# confirming the price.
item = SaleItem.objects.get(type="your-sale-item")
sale = {
"amount": str(item.amount),
"payment_method_nonce": payment_details[‘nonce’],
"descriptor": {"name": "BIZ*NAME"},
"options": {
"paypal": {"description": "BizName (bizname.foo)"},
# Note that setting this to true indicates we want both authorization and immediate settlement of this charge
"submit_for_settlement": True
}
}

# Perform transaction, making the remote request to braintree
payment_result = braintree.Transaction.sale(sale)

if payment_result.is_success is not True:
if not hasattr(payment_result, ‘transation’):
"""
If the transaction was not successfully processed by braintree,
the list of possible errors is staggeringly long:
@see https://developers.braintreepayments.com/reference/general/validation-errors/all/python#transaction
You’ll likely want to iterate through the deep_errors list and handle them appropriately.
"""
return JsonResponse(status=500)
else:
"""
If the transacation was successfully processed by braintree, but was rejected by
the payment processor, you’ll want to handle this rejection appropriately.
@see https://developers.braintreepayments.com/reference/general/processor-responses/settlement-responses
"""
return JsonResponse(status=500)

transaction = payment_result.transaction
if transaction.amount < item.amount:
# Confirm you actually were paid as much as your item costs! If you don’t submit for settlement,
# or only submit for partial settlement, you should skip this check.
return JsonResponse(status=500)

# Transaction successful – you can now proceed to create a new user, produce a download link for an item,
# or whatever else it is you need payment processing for.
# You can also now create a local reference for the transaction, in case you ever need to look it up for refund
# purposes, for example.
TransactionRecord.objects.create(item=item, amount=transaction.amount,
transaction_id=transaction.id, type=transaction.payment_instrument_type)

return JsonResponse(status=200)
[/code]

Some Assembly Required

And there, we’re done!

…for a given value of done. Although we now have something approaching the bare minimum for processing a transaction, what we have doesn’t account for recurring subscriptions, storing customer information in the braintree ‘vault’, sub-merchants, handling refunds, performing authorizations and settling at a future time…

For more information, I direct you once again to braintree’s well-written developer documentation, and I would suggest directing any questions you may have to StackOverflow, where I’ve noted braintree developers to be fairly quick to respond with assistance.

At this point, however, you should have the beginnings of a working implementation. Soon, your users will be happily trading in their useless monetary units for the happiness and lasting contentment that only cat pictures can provide. Cheers!

 

Cat image: CC-BY licensed, via https://www.flickr.com/photos/53911972@N03/

Christopher Keefer

Christopher Keefer

Christopher Keefer is a Senior Software Engineer at Art+Logic. He generally spends his spare time on the computer too, so there isn't much hope for him.
Christopher Keefer

Latest posts by Christopher Keefer (see all)

Tags:

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.